Mercurial > pub > ImplabNet
changeset 224:1ba2127cfcd8 v2
Слияние с default
author | cin |
---|---|
date | Tue, 22 Aug 2017 12:45:01 +0300 |
parents | 8808383fcb94 (diff) 27ea7f07e2e4 (current diff) |
children | 8222a2ab3ab7 |
files | Implab/Implab.csproj |
diffstat | 54 files changed, 2115 insertions(+), 569 deletions(-) [+] |
line wrap: on
line diff
--- a/Implab.Diagnostics.Interactive/InteractiveListener.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Diagnostics.Interactive/InteractiveListener.cs Tue Aug 22 12:45:01 2017 +0300 @@ -17,7 +17,6 @@ readonly Promise m_guiStarted = new Promise(); readonly IPromise m_guiFinished; - // readonly IPromise m_workerFinished = new Promise<object>(); readonly MTQueue<TraceViewItem> m_queue = new MTQueue<TraceViewItem>(); readonly AutoResetEvent m_queueEvent = new AutoResetEvent(false); @@ -30,8 +29,8 @@ readonly ManualResetEvent m_pauseEvent = new ManualResetEvent(true); public InteractiveListener() { - m_guiFinished = AsyncPool.RunThread(GuiThread); - /*m_workerFinished = */AsyncPool.RunThread(QueueThread); + m_guiFinished = RunGuiThread(); + AsyncPool.RunThread(QueueThread); m_guiStarted.Join(); } @@ -56,13 +55,38 @@ if (m_queue.TryDequeue(out item)) { Interlocked.Decrement(ref m_queueLength); - m_syncGuiThread.Send(x => m_form.AddTraceEvent(item),null); + m_syncGuiThread.Post(x => m_form.AddTraceEvent(item),null); } else { m_queueEvent.WaitOne(); } } } + public IPromise RunGuiThread() { + var p = new Promise(); + + var caller = TraceContext.Instance.CurrentOperation; + + var worker = new Thread(() => { + TraceContext.Instance.EnterLogicalOperation(caller, false); + try { + Application.OleRequired(); + GuiThread(); + p.Resolve(); + } catch (Exception e) { + p.Reject(e); + } finally { + TraceContext.Instance.Leave(); + } + }); + worker.SetApartmentState(ApartmentState.STA); + worker.IsBackground = true; + worker.Name = string.Format("{0} GUI Thread", nameof(InteractiveListener)); + worker.Start(); + + return p; + } + public void Pause() { // for consistency we need to set this properties atomically lock (m_pauseLock) { @@ -112,8 +136,9 @@ Indent = args.Operation.Level, Message = entry.ToString(), Thread = args.ThreadId, - Channel = args.ChannelName, - Timestamp = Environment.TickCount + Channel = args.Channel.ToString(), + Timestamp = Environment.TickCount, + TimeDelta = args.OperationTimeOffset }; Enqueue(item);
--- a/Implab.Diagnostics.Interactive/TraceForm.Designer.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Diagnostics.Interactive/TraceForm.Designer.cs Tue Aug 22 12:45:01 2017 +0300 @@ -23,13 +23,14 @@ /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); this.eventsDataGrid = new System.Windows.Forms.DataGridView(); - this.traceViewItemBindingSource = new System.Windows.Forms.BindingSource(this.components); + this.traceViewItemBindingSource = new System.Windows.Forms.BindingSource(); this.threadDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.TimeDelta = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.Channel = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.messageDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); ((System.ComponentModel.ISupportInitialize)(this.eventsDataGrid)).BeginInit(); @@ -56,20 +57,22 @@ this.eventsDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.eventsDataGrid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.threadDataGridViewTextBoxColumn, + this.TimeDelta, this.Channel, this.messageDataGridViewTextBoxColumn}); this.eventsDataGrid.DataSource = this.traceViewItemBindingSource; - dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window; - dataGridViewCellStyle3.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.ControlText; - dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight; - dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText; - dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False; - this.eventsDataGrid.DefaultCellStyle = dataGridViewCellStyle3; + dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle4.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle4.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); + dataGridViewCellStyle4.ForeColor = System.Drawing.SystemColors.ControlText; + dataGridViewCellStyle4.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle4.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.eventsDataGrid.DefaultCellStyle = dataGridViewCellStyle4; this.eventsDataGrid.Location = new System.Drawing.Point(12, 12); this.eventsDataGrid.Name = "eventsDataGrid"; this.eventsDataGrid.ReadOnly = true; + this.eventsDataGrid.RowHeadersVisible = false; this.eventsDataGrid.RowHeadersWidth = 17; this.eventsDataGrid.RowTemplate.Resizable = System.Windows.Forms.DataGridViewTriState.False; this.eventsDataGrid.Size = new System.Drawing.Size(939, 480); @@ -82,31 +85,43 @@ // // threadDataGridViewTextBoxColumn // - this.threadDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCellsExceptHeader; + this.threadDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.threadDataGridViewTextBoxColumn.DataPropertyName = "Thread"; this.threadDataGridViewTextBoxColumn.HeaderText = "TID"; this.threadDataGridViewTextBoxColumn.Name = "threadDataGridViewTextBoxColumn"; this.threadDataGridViewTextBoxColumn.ReadOnly = true; - this.threadDataGridViewTextBoxColumn.Width = 5; + this.threadDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.threadDataGridViewTextBoxColumn.Width = 32; + // + // TimeDelta + // + this.TimeDelta.DataPropertyName = "TimeDelta"; + dataGridViewCellStyle2.Format = "\'+\' 0 \'ms\'"; + dataGridViewCellStyle2.NullValue = null; + this.TimeDelta.DefaultCellStyle = dataGridViewCellStyle2; + this.TimeDelta.HeaderText = "TimeDelta"; + this.TimeDelta.Name = "TimeDelta"; + this.TimeDelta.ReadOnly = true; + this.TimeDelta.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; // // Channel // - this.Channel.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells; this.Channel.DataPropertyName = "Channel"; this.Channel.HeaderText = "Channel"; this.Channel.Name = "Channel"; this.Channel.ReadOnly = true; - this.Channel.Width = 79; + this.Channel.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; // // messageDataGridViewTextBoxColumn // this.messageDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; this.messageDataGridViewTextBoxColumn.DataPropertyName = "FormattedMessage"; - dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; - this.messageDataGridViewTextBoxColumn.DefaultCellStyle = dataGridViewCellStyle2; + dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.messageDataGridViewTextBoxColumn.DefaultCellStyle = dataGridViewCellStyle3; this.messageDataGridViewTextBoxColumn.HeaderText = "Message"; this.messageDataGridViewTextBoxColumn.Name = "messageDataGridViewTextBoxColumn"; this.messageDataGridViewTextBoxColumn.ReadOnly = true; + this.messageDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; // // TraceForm // @@ -127,8 +142,8 @@ private System.Windows.Forms.DataGridView eventsDataGrid; private System.Windows.Forms.BindingSource traceViewItemBindingSource; private System.Windows.Forms.DataGridViewTextBoxColumn threadDataGridViewTextBoxColumn; + private System.Windows.Forms.DataGridViewTextBoxColumn TimeDelta; private System.Windows.Forms.DataGridViewTextBoxColumn Channel; private System.Windows.Forms.DataGridViewTextBoxColumn messageDataGridViewTextBoxColumn; - } } \ No newline at end of file
--- a/Implab.Diagnostics.Interactive/TraceForm.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Diagnostics.Interactive/TraceForm.cs Tue Aug 22 12:45:01 2017 +0300 @@ -31,7 +31,8 @@ public void AddTraceEvent(TraceViewItem item) { traceViewItemBindingSource.Add(item); - eventsDataGrid.FirstDisplayedScrollingRowIndex = eventsDataGrid.RowCount - 1; + if(eventsDataGrid.RowCount > 0) + eventsDataGrid.FirstDisplayedScrollingRowIndex = eventsDataGrid.RowCount - 1; } Color GetThreadColor(int thread) {
--- a/Implab.Diagnostics.Interactive/TraceForm.resx Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Diagnostics.Interactive/TraceForm.resx Tue Aug 22 12:45:01 2017 +0300 @@ -117,6 +117,9 @@ <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> + <metadata name="TimeDelta.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>True</value> + </metadata> <metadata name="Channel.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <value>True</value> </metadata>
--- a/Implab.Diagnostics.Interactive/TraceViewItem.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Diagnostics.Interactive/TraceViewItem.cs Tue Aug 22 12:45:01 2017 +0300 @@ -9,6 +9,7 @@ string m_formattedValue; public string Message { get; set; } + public int TimeDelta { get; set; } public int Timestamp { get; set; } public int Indent { get; set; } public int Thread { get; set; }
--- a/Implab.Fx.Test/Implab.Fx.Test.csproj Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Fx.Test/Implab.Fx.Test.csproj Tue Aug 22 12:45:01 2017 +0300 @@ -80,6 +80,7 @@ <Compile Include="Sample\OverlayForm.Designer.cs"> <DependentUpon>OverlayForm.cs</DependentUpon> </Compile> + <Compile Include="StaApartmentTests.cs" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="Sample\MainForm.resx">
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Fx.Test/StaApartmentTests.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,52 @@ +using System; +using System.Reflection; +using System.Threading; +using Implab.Parallels; +using Implab.Components; + +#if MONO + +using NUnit.Framework; +using TestClassAttribute = NUnit.Framework.TestFixtureAttribute; +using TestMethodAttribute = NUnit.Framework.TestAttribute; +using AssertFailedException = NUnit.Framework.AssertionException; +#else + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +#endif +namespace Implab.Fx.Test { + [TestClass] + public class StaApartmentTests { + [TestMethod] + public void CreateDestroyApartment() { + var apartment = new StaApartment(); + try { + Assert.IsNotNull(apartment.SyncContext); + Assert.Fail(); + } catch (InvalidOperationException) { + // OK + } + + apartment.Start().Join(); + Assert.AreEqual(apartment.State, ExecutionState.Running); + + Assert.IsNotNull(apartment.SyncContext); + apartment.Stop().Join(); + + Assert.IsTrue(apartment.State == ExecutionState.Disposed); + } + + [TestMethod] + public void InvokeInApartment() { + var apartment = new StaApartment(); + + apartment.Start().Join(); + + var apType = apartment.Invoke(() => { return Thread.CurrentThread.GetApartmentState(); }).Join(); + Assert.AreEqual(apType, ApartmentState.STA); + + apartment.Stop().Join(); + } + } +}
--- a/Implab.Fx/Implab.Fx.csproj Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Fx/Implab.Fx.csproj Tue Aug 22 12:45:01 2017 +0300 @@ -70,6 +70,7 @@ <Compile Include="PromiseHelpers.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="ControlBoundPromise.cs" /> + <Compile Include="StaApartment.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Implab\Implab.csproj">
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Fx/StaApartment.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,204 @@ +using Implab.Components; +using Implab.Diagnostics; +using Implab.Parallels; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Implab.Fx { + public class StaApartment : RunnableComponent { + readonly Thread m_worker; + SynchronizationContext m_syncContext; + SyncContextPromise m_enterPromise; + + readonly Promise m_threadStarted; + readonly Promise m_threadTerminated; + + public StaApartment() : base(true) { + m_threadStarted = new Promise(); + m_threadTerminated = new Promise(); + + m_worker = new Thread(WorkerEntry); + m_worker.SetApartmentState(ApartmentState.STA); + m_worker.IsBackground = true; + m_worker.Name = "STA managed aparment"; + } + + public SynchronizationContext SyncContext { + get { + if (m_syncContext == null) + throw new InvalidOperationException(); + return m_syncContext; + } + } + + /// <summary> + /// Returns the promise which will dispatch all handlers inside the apartment using it's <see cref="SynchronizationContext"/> + /// </summary> + /// <remarks> + /// Current implementation is optimized and will lost aync operation stack + /// </remarks> + /// <returns>The promise</returns> + public IPromise Enter() { + if (m_enterPromise == null) + throw new InvalidOperationException(); + return m_enterPromise; + } + + public IPromise Invoke(Action<ICancellationToken> action) { + Safe.ArgumentNotNull(action, "action"); + + if (m_syncContext == null) + throw new InvalidOperationException(); + var p = new Promise(); + var lop = TraceContext.Instance.CurrentOperation; + + m_syncContext.Post(x => { + TraceContext.Instance.EnterLogicalOperation(lop, false); + try { + if (p.CancelOperationIfRequested()) + return; + + action(p); + p.Resolve(); + } catch (Exception e) { + p.Reject(e); + } finally { + TraceContext.Instance.Leave(); + } + }, null); + + return p; + } + + public IPromise<T> Invoke<T>(Func<ICancellationToken, T> action) { + Safe.ArgumentNotNull(action, "action"); + + if (m_syncContext == null) + throw new InvalidOperationException(); + var p = new Promise<T>(); + var lop = TraceContext.Instance.CurrentOperation; + + m_syncContext.Post(x => { + TraceContext.Instance.EnterLogicalOperation(lop, false); + try { + if (p.CancelOperationIfRequested()) + return; + p.Resolve(action(p)); + } catch (Exception e) { + p.Reject(e); + } finally { + TraceContext.Instance.Leave(); + } + }, null); + + return p; + } + + public IPromise Invoke(Action action) { + Safe.ArgumentNotNull(action, "action"); + + if (m_syncContext == null) + throw new InvalidOperationException(); + var p = new Promise(); + var lop = TraceContext.Instance.CurrentOperation; + + m_syncContext.Post(x => { + TraceContext.Instance.EnterLogicalOperation(lop, false); + try { + if (p.CancelOperationIfRequested()) + return; + action(); + p.Resolve(); + } catch (Exception e) { + p.Reject(e); + } finally { + TraceContext.Instance.Leave(); + } + }, null); + + return p; + } + + public IPromise<T> Invoke<T>(Func<T> action) { + Safe.ArgumentNotNull(action, "action"); + + if (m_syncContext == null) + throw new InvalidOperationException(); + var p = new Promise<T>(); + var lop = TraceContext.Instance.CurrentOperation; + + m_syncContext.Post(x => { + TraceContext.Instance.EnterLogicalOperation(lop, false); + try { + if (p.CancelOperationIfRequested()) + return; + p.Resolve(action()); + } catch (Exception e) { + p.Reject(e); + } finally { + TraceContext.Instance.Leave(); + } + }, null); + + return p; + } + + + /// <summary> + /// Starts the apartment thread + /// </summary> + /// <returns>Promise which will be fullfiled when the syncronization + /// context will be ready to accept tasks.</returns> + protected override IPromise OnStart() { + m_worker.Start(); + return m_threadStarted; + } + + /// <summary> + /// Posts quit message to the message loop of the apartment + /// </summary> + /// <returns>Promise</returns> + protected override IPromise OnStop() { + m_syncContext.Post(x => Application.ExitThread(), null); + return m_threadTerminated; + } + + void WorkerEntry() { + m_syncContext = new WindowsFormsSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(m_syncContext); + m_enterPromise = new SyncContextPromise(m_syncContext); + m_threadStarted.Resolve(); + m_enterPromise.Resolve(); + + Application.OleRequired(); + Application.Run(); + + try { + OnShutdown(); + m_threadTerminated.Resolve(); + } catch(Exception err) { + m_threadTerminated.Reject(err); + } + } + + /// <summary> + /// Called from the STA apartment after the message loop is terminated, override this + /// method to handle apartment cleanup. + /// </summary> + protected virtual void OnShutdown() { + } + + protected override void Dispose(bool disposing) { + if (disposing) { + if (!m_threadTerminated.IsResolved) + m_syncContext.Post(x => Application.ExitThread(), null); + } + base.Dispose(disposing); + } + } +}
--- a/Implab.Test/AsyncTests.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Test/AsyncTests.cs Tue Aug 22 12:45:01 2017 +0300 @@ -336,7 +336,7 @@ Console.WriteLine("done reader #2: {0} ms", Environment.TickCount - t1); } ) - .Bundle() + .PromiseAll() .Join(); Assert.AreEqual(count * 3, res1 + res2); @@ -414,7 +414,7 @@ Console.WriteLine("done reader #2: {0} ms", Environment.TickCount - t1); } ) - .Bundle() + .PromiseAll() .Join(); Assert.AreEqual(summ , r1 + r2); @@ -490,7 +490,7 @@ Console.WriteLine("done reader #2: {0} ms, avg chunk size: {1}", Environment.TickCount - t1, avgchunk); } ) - .Bundle() + .PromiseAll() .Join(); Assert.AreEqual(summ , r1 + r2); @@ -593,7 +593,7 @@ Console.WriteLine("done reader #2: {0} ms, {1} items", Environment.TickCount - t1, count); } ) - .Bundle() + .PromiseAll() .Join(); Assert.AreEqual(summ , r1 + r2); @@ -835,7 +835,7 @@ log.Enqueue("Writer #1 lock released"); } } - ).Bundle().Join(1000); + ).PromiseAll().Join(1000); log.Enqueue("Done"); } catch(Exception error) { log.Enqueue(error.Message);
--- a/Implab.Test/Implab.Format.Test/Implab.Format.Test.csproj Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Test/Implab.Format.Test/Implab.Format.Test.csproj Tue Aug 22 12:45:01 2017 +0300 @@ -1,52 +1,55 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProductVersion>8.0.30703</ProductVersion> - <SchemaVersion>2.0</SchemaVersion> - <ProjectGuid>{4D364996-7ECD-4193-8F90-F223FFEA49DA}</ProjectGuid> - <OutputType>Library</OutputType> - <RootNamespace>Implab.Format.Test</RootNamespace> - <AssemblyName>Implab.Format.Test</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <ReleaseVersion>0.2</ReleaseVersion> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug</OutputPath> - <DefineConstants>DEBUG;</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release</OutputPath> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <ItemGroup> - <Reference Include="System" /> - <Reference Include="nunit.framework"> - <HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath> - </Reference> - </ItemGroup> - <ItemGroup> - <Compile Include="JsonTests.cs" /> - </ItemGroup> - <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> - <ItemGroup> - <ProjectReference Include="..\..\Implab\Implab.csproj"> - <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project> - <Name>Implab</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> - <None Include="packages.config" /> - </ItemGroup> +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>8.0.30703</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{4D364996-7ECD-4193-8F90-F223FFEA49DA}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>Implab.Format.Test</RootNamespace> + <AssemblyName>Implab.Format.Test</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <ReleaseVersion>0.2</ReleaseVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>full</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="nunit.framework"> + <HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="JsonTests.cs" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <ItemGroup> + <ProjectReference Include="..\..\Implab\Implab.csproj"> + <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project> + <Name>Implab</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> + </ItemGroup> </Project> \ No newline at end of file
--- a/Implab.Test/Implab.Test.csproj Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Test/Implab.Test.csproj Tue Aug 22 12:45:01 2017 +0300 @@ -63,6 +63,9 @@ <ItemGroup> <Compile Include="AsyncTests.cs" /> <Compile Include="CancelationTests.cs" /> + <Compile Include="Mock\MockPollingComponent.cs" /> + <Compile Include="Mock\MockRunnableComponent.cs" /> + <Compile Include="PollingComponentTests.cs" /> <Compile Include="PromiseHelper.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="RunnableComponentTests.cs" /> @@ -73,6 +76,9 @@ <Name>Implab</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <Folder Include="Implab.Format.Test\" /> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets.
--- a/Implab.Test/Implab.Test.mono.csproj Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Test/Implab.Test.mono.csproj Tue Aug 22 12:45:01 2017 +0300 @@ -1,69 +1,81 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProductVersion>8.0.30703</ProductVersion> - <SchemaVersion>2.0</SchemaVersion> - <ProjectGuid>{2BD05F84-E067-4B87-9477-FDC2676A21C6}</ProjectGuid> - <OutputType>Library</OutputType> - <RootNamespace>Implab.Test</RootNamespace> - <AssemblyName>Implab.Test</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <ReleaseVersion>0.2</ReleaseVersion> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug</OutputPath> - <DefineConstants>DEBUG;MONO</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <Optimize>true</Optimize> - <OutputPath>bin\Release</OutputPath> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - <DefineConstants>MONO</DefineConstants> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug 4.5|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug</OutputPath> - <DefineConstants>DEBUG;TRACE;NET_4_5;MONO</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release 4.5|AnyCPU' "> - <Optimize>true</Optimize> - <OutputPath>bin\Release</OutputPath> - <DefineConstants>NET_4_5;MONO</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <ItemGroup> - <Reference Include="System" /> - <Reference Include="nunit.framework" /> - </ItemGroup> - <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> - <ItemGroup> - <Compile Include="AsyncTests.cs" /> - <Compile Include="PromiseHelper.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="CancelationTests.cs" /> - <Compile Include="RunnableComponentTests.cs" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\Implab\Implab.csproj"> - <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project> - <Name>Implab</Name> - </ProjectReference> - </ItemGroup> +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>8.0.30703</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{2BD05F84-E067-4B87-9477-FDC2676A21C6}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>Implab.Test</RootNamespace> + <AssemblyName>Implab.Test</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <ReleaseVersion>0.2</ReleaseVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;MONO</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + <DefineConstants>MONO</DefineConstants> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug 4.5|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;TRACE;NET_4_5;MONO</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release 4.5|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <DefineConstants>NET_4_5;MONO</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL"> + <HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <ItemGroup> + <Compile Include="AsyncTests.cs" /> + <Compile Include="PromiseHelper.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="CancelationTests.cs" /> + <Compile Include="RunnableComponentTests.cs" /> + <Compile Include="PollingComponentTests.cs" /> + <Compile Include="Mock\MockRunnableComponent.cs" /> + <Compile Include="Mock\MockPollingComponent.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Implab\Implab.csproj"> + <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project> + <Name>Implab</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> </Project> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/Mock/MockPollingComponent.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,71 @@ +using System; +using Implab.Components; + +namespace Implab.Test.Mock { + class MockPollingComponent : PollingComponent { + public MockPollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(interval, dispatcher, initialized) { + } + + public Action MockInit { + get; + set; + } + + public Action<Exception> MockOnError { + get; + set; + } + + public Action<Exception> MockOnCancel { + get; + set; + } + + public Func<IPromise> MockStart { + get; + set; + } + + public Func<IPromise> MockStop { + get; + set; + } + + public Func<ICancellationToken, IPromise> MockTick { + get; + set; + } + + protected override IPromise OnStart() { + return MockStart != null ? Safe.Run(MockStart).Chain(base.OnStart) : Safe.Run(base.OnStart); + } + + protected override IPromise OnStop() { + return MockStop != null ? Safe.Run(MockStop).Chain(base.OnStop) : Safe.Run(base.OnStop); + } + + protected override void OnInitialize() { + if (MockInit != null) + MockInit(); + } + + protected override IPromise OnTick(ICancellationToken cancellationToken) { + return MockTick != null ? Safe.Run(() => MockTick(cancellationToken)) : Promise.Success; + } + + protected override void OnTickCancel(Exception error) { + if (MockOnCancel != null) + MockOnCancel(error); + } + + protected override void OnTickError(Exception error) { + if (MockOnError != null) + MockOnError(error); + } + + public void CallComponentFail(Exception error) { + Fail(error); + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/Mock/MockRunnableComponent.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,52 @@ +using System; +using Implab.Components; + +namespace Implab.Test.Mock { + class MockRunnableComponent : RunnableComponent { + public MockRunnableComponent(bool initialized) : base(initialized) { + } + + public MockRunnableComponent(bool initialized, bool reusable) : base(initialized, reusable) { + } + + public Action MockInit { + get; + set; + } + + public Func<IPromise> MockStart { + get; + set; + } + + public Func<IPromise> MockStop { + get; + set; + } + + public Action<bool> MockDispose { + get; + set; + } + + protected override IPromise OnStart() { + return MockStart != null ? Safe.Run(MockStart).Chain(base.OnStart) : Safe.Run(base.OnStart); + } + + protected override IPromise OnStop() { + return MockStop != null ? Safe.Run(MockStop).Chain(base.OnStop) : Safe.Run(base.OnStop); + } + + protected override void OnInitialize() { + if (MockInit != null) + MockInit(); + } + + protected override void Dispose(bool disposing) { + if (MockDispose != null) + MockDispose(disposing); + base.Dispose(disposing); + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/PollingComponentTests.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,230 @@ +using System; +using System.Reflection; +using System.Threading; +using Implab.Parallels; +using Implab.Components; +using Implab.Test.Mock; + +#if MONO + +using NUnit.Framework; +using TestClassAttribute = NUnit.Framework.TestFixtureAttribute; +using TestMethodAttribute = NUnit.Framework.TestAttribute; +using AssertFailedException = NUnit.Framework.AssertionException; +#else + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +#endif + +namespace Implab.Test { + [TestClass] + public class PollingComponentTests { + static void ShouldThrow(Action action) { + try { + action(); + Assert.Fail(); + } catch (AssertFailedException) { + throw; + } catch { + } + } + + [TestMethod] + public void NormalFlowTest() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, false); + + Assert.AreEqual(ExecutionState.Created, comp.State); + + comp.Initialize(); + + Assert.AreEqual(ExecutionState.Ready, comp.State); + + comp.Start().Join(1000); + + Assert.AreEqual(ExecutionState.Running, comp.State); + + comp.Stop().Join(1000); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + + } + + [TestMethod] + public void ShouldStartTicks() { + var signal = new Signal(); + + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + comp.MockTick = ct => { + signal.Set(); + return Promise.Success; + }; + + comp.Start().Join(1000); + signal.Wait(1000); + comp.Stop().Join(1000); + } + + [TestMethod] + public void StopShouldWaitForTickToComplete() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var signal = new Signal(); + var promise = new Promise(); + + // timer should tick once + comp.MockTick = ct => { + signal.Set(); + return promise; + }; + + // start timer + comp.Start().Join(1000); + + signal.Wait(); // wait for tick + + // try to stop component + var stopping = comp.Stop(); + + Assert.AreEqual(ExecutionState.Stopping, comp.State); + ShouldThrow(() => stopping.Join(100)); + Assert.AreEqual(ExecutionState.Stopping, comp.State); + + // complete operation + promise.Resolve(); + + // the component should stop normally + stopping.Join(1000); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + [TestMethod] + public void ShouldRecoverAfterTickError() { + var ticks = 0; + + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var signal = new Signal(); // will signal when timer fires 10 times + + comp.MockTick = ct => { + ticks++; + if (ticks == 10) + signal.Set(); + // each time handler dies + throw new Exception("tainted handler"); + }; + + comp.Start(); + + signal.Wait(1000); + + comp.Stop().Join(1000); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + [TestMethod] + public void StopCancelHandlerOnStop() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var started = new Signal(); + bool cancelled = false; + + // timer should tick once + comp.MockTick = ct => { + started.Set(); + + while(!ct.IsCancellationRequested) { + Thread.Sleep(1); + } + + cancelled = true; + + throw new OperationCanceledException(); + }; + + // start timer + comp.Start().Join(1000); + + started.Wait(); // wait for tick + + // try to stop component + comp.Stop().Join(1000); + + Assert.AreEqual(true, cancelled); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + [TestMethod] + public void FailTickOnStopShouldBeIgnored() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var started = new Signal(); + var finish = new Signal(); + + // timer should tick once + comp.MockTick = ct => { + started.Set(); + finish.Wait(); + // component is in stopping state here + throw new Exception("Die, die, die!!!"); + }; + + + comp.MockOnError = comp.CallComponentFail; + + // start timer + comp.Start().Join(1000); + + started.Wait(); // wait for tick + + // try to stop component + var stopping = comp.Stop(); + + // the component is in stopping state but it is waiting for the tick handler to complete + finish.Set(); // signal the tick handler to finish + + // tick handler should stop rather soon + stopping.Join(1000); + + // validate the component is disposed + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + [TestMethod] + public void FailTickShouldFailComponent() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var started = new Signal(); + var finish = new Signal(); + + // timer should tick once + comp.MockTick = ct => { + started.Set(); + throw new Exception("Die, die, die!!!"); + }; + + + comp.MockOnError = err => { + comp.CallComponentFail(err); + finish.Set(); + }; + + // start timer + comp.Start().Join(1000); + + started.Wait(); // wait for tick + + finish.Wait(); + + // try to stop component + ShouldThrow(() => comp.Stop()); + + Assert.AreEqual(ExecutionState.Failed, comp.State); + Assert.IsNotNull(comp.LastError); + Assert.AreEqual("Die, die, die!!!", comp.LastError.Message); + + comp.Dispose(); + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + } +} +
--- a/Implab.Test/PromiseHelper.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Test/PromiseHelper.cs Tue Aug 22 12:45:01 2017 +0300 @@ -10,5 +10,13 @@ return retVal; }); } + + public static IPromise Sleep(int timeout) { + return AsyncPool.Invoke((ct) => { + ct.CancellationRequested(ct.CancelOperation); + Thread.Sleep(timeout); + return 0; + }); + } } }
--- a/Implab.Test/RunnableComponentTests.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab.Test/RunnableComponentTests.cs Tue Aug 22 12:45:01 2017 +0300 @@ -3,6 +3,7 @@ using System.Threading; using Implab.Parallels; using Implab.Components; +using Implab.Test.Mock; #if MONO @@ -30,46 +31,15 @@ } } - class Runnable : RunnableComponent { - public Runnable(bool initialized) : base(initialized) { - } - - public Action MockInit { - get; - set; - } - public Func<IPromise> MockStart { - get; - set; - } - - public Func<IPromise> MockStop { - get; - set; - } - - protected override IPromise OnStart() { - return MockStart != null ? MockStart() : base.OnStart(); - } - - protected override IPromise OnStop() { - return MockStop != null ? MockStop() : base.OnStart(); - } - - protected override void OnInitialize() { - if (MockInit != null) - MockInit(); - } - } [TestMethod] public void NormalFlowTest() { - var comp = new Runnable(false); + var comp = new MockRunnableComponent(false); Assert.AreEqual(ExecutionState.Created, comp.State); - comp.Init(); + comp.Initialize(); Assert.AreEqual(ExecutionState.Ready, comp.State); @@ -85,7 +55,7 @@ [TestMethod] public void InitFailTest() { - var comp = new Runnable(false) { + var comp = new MockRunnableComponent(false) { MockInit = () => { throw new Exception("BAD"); } @@ -95,7 +65,7 @@ ShouldThrow(() => comp.Stop()); Assert.AreEqual(ExecutionState.Created, comp.State); - ShouldThrow(comp.Init); + ShouldThrow(comp.Initialize); Assert.AreEqual(ExecutionState.Failed, comp.State); @@ -110,19 +80,82 @@ [TestMethod] public void DisposedTest() { - var comp = new Runnable(false); + var comp = new MockRunnableComponent(false); comp.Dispose(); ShouldThrow(() => comp.Start()); ShouldThrow(() => comp.Stop()); - ShouldThrow(comp.Init); + ShouldThrow(comp.Initialize); Assert.AreEqual(ExecutionState.Disposed, comp.State); } [TestMethod] + public void ShouldCallDisposeOnStop() { + var comp = new MockRunnableComponent(true); + + bool disposed = false; + comp.MockDispose = (disposing) => { + disposed = true; + }; + + comp.Start().Join(1000); + comp.Stop().Join(1000); + + ShouldThrow(() => comp.Start()); + ShouldThrow(() => comp.Stop()); + ShouldThrow(comp.Initialize); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + Assert.IsTrue(disposed); + } + + [TestMethod] + public void ShouldNotCallDisposeOnStop() { + var comp = new MockRunnableComponent(true, true); + + bool disposed = false; + comp.MockDispose = (disposing) => { + disposed = true; + }; + + comp.Start().Join(1000); + comp.Stop().Join(1000); + + Assert.AreEqual(ExecutionState.Ready, comp.State); + Assert.IsFalse(disposed); + } + + [TestMethod] + public void SelfDisposeOnStop() { + var comp = new MockRunnableComponent(true, true); + + bool disposed = false; + comp.MockDispose = (disposing) => { + disposed = true; + }; + + comp.Start().Join(1000); + comp.Stop().Join(1000); + + Assert.AreEqual(ExecutionState.Ready, comp.State); + Assert.IsFalse(disposed); + + comp.MockStop = () => { + comp.Dispose(); + return Promise.Success; + }; + + comp.Start().Join(1000); + comp.Stop().Join(1000); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + Assert.IsTrue(disposed); + } + + [TestMethod] public void StartCancelTest() { - var comp = new Runnable(true) { + var comp = new MockRunnableComponent(true) { MockStart = () => PromiseHelper.Sleep(100000, 0) }; @@ -140,7 +173,7 @@ [TestMethod] public void StartStopTest() { var stop = new Signal(); - var comp = new Runnable(true) { + var comp = new MockRunnableComponent(true) { MockStart = () => PromiseHelper.Sleep(100000, 0), MockStop = () => AsyncPool.RunThread(stop.Wait) }; @@ -160,7 +193,7 @@ [TestMethod] public void StartStopFailTest() { - var comp = new Runnable(true) { + var comp = new MockRunnableComponent(true) { MockStart = () => PromiseHelper.Sleep(100000, 0).Then(null,null,x => { throw new Exception("I'm dead"); }) }; @@ -175,7 +208,7 @@ [TestMethod] public void StopCancelTest() { - var comp = new Runnable(true) { + var comp = new MockRunnableComponent(true) { MockStop = () => PromiseHelper.Sleep(100000, 0) };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/packages.config Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="NUnit" version="2.6.4" targetFramework="net45" /> +</packages> \ No newline at end of file
--- a/Implab/Automaton/AutomatonTransition.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Automaton/AutomatonTransition.cs Tue Aug 22 12:45:01 2017 +0300 @@ -28,6 +28,14 @@ public override int GetHashCode() { return s1 + s2 + edge; } + + public static bool operator == (AutomatonTransition rv, AutomatonTransition lv) { + return rv.Equals(lv); + } + + public static bool operator !=(AutomatonTransition rv, AutomatonTransition lv) { + return rv.Equals(lv); + } } }
--- a/Implab/Components/ComponentContainer.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Components/ComponentContainer.cs Tue Aug 22 12:45:01 2017 +0300 @@ -8,7 +8,8 @@ /// </summary> /// <remarks>Instanses of this class are thread safe.</remarks> public class ComponentContainer<T> : Disposable, ICollection<T> { - readonly HashSet<T> m_components = new HashSet<T>(); + List<T> m_components = new List<T>(); + readonly object m_lock = new object(); /// <summary> /// Removes currently stored compoenents from the container and disposes them if possible. @@ -17,12 +18,11 @@ /// A new components may be added before this method completes. /// </remarks> public void Clear() { - T[] removed; + List<T> removed; - lock (m_components) { - removed = new T[m_components.Count]; - m_components.CopyTo(removed); - m_components.Clear(); + lock (m_lock) { + removed = m_components; + m_components = new List<T>(); } foreach (var item in removed.OfType<IDisposable>()) @@ -34,7 +34,7 @@ /// </summary> /// <param name="item">The item to check.</param> public bool Contains(T item) { - lock (m_components) + lock (m_lock) return m_components.Contains(item); } @@ -44,7 +44,7 @@ /// <param name="array">A destination array for components.</param> /// <param name="arrayIndex">A starting index in the destination array.</param> public void CopyTo(T[] array, int arrayIndex) { - lock (m_components) + lock (m_lock) m_components.CopyTo(array, arrayIndex); } @@ -53,7 +53,7 @@ /// </summary> /// <param name="item">The item to remove.</param> public bool Remove(T item) { - lock (m_components) + lock (m_lock) return m_components.Remove(item); } @@ -62,7 +62,7 @@ /// </summary> public int Count { get { - lock (m_components) + lock (m_lock) return m_components.Count; } } @@ -84,12 +84,11 @@ /// </summary> /// <returns>The enumerator.</returns> public IEnumerator<T> GetEnumerator() { - T[] items; - lock (m_components) { - items = new T[m_components.Count]; + T[] items = new T[m_components.Count]; + lock (m_lock) { m_components.CopyTo(items); - return (IEnumerator<T>)items.GetEnumerator(); } + return (IEnumerator<T>)items.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { @@ -105,13 +104,15 @@ /// </remarks> public void Add(T item) { Safe.ArgumentNotNull(item, "item"); - - lock (m_components) { + bool dispose = false; + lock (m_lock) { if (IsDisposed) - Safe.Dispose(item); + dispose = true; else m_components.Add(item); } + if (dispose) + Safe.Dispose(item); } /// <summary> @@ -119,8 +120,10 @@ /// </summary> /// <param name="disposing">If set to <c>true</c> the collection is disposing.</param> protected override void Dispose(bool disposing) { + if (disposing) + Clear(); + base.Dispose(disposing); - Clear(); } } }
--- a/Implab/Components/Disposable.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Components/Disposable.cs Tue Aug 22 12:45:01 2017 +0300 @@ -1,5 +1,6 @@ using Implab.Diagnostics; using System; +using System.Diagnostics.CodeAnalysis; using System.Threading; namespace Implab.Components { @@ -7,7 +8,7 @@ /// Base class the objects which support disposing. /// </summary> public class Disposable : IDisposable { - + int m_disposed; public event EventHandler Disposed; @@ -74,13 +75,12 @@ /// из нескольких потоков. /// </remarks> protected virtual void Dispose(bool disposing) { - if (disposing) { - EventHandler temp = Disposed; - if (temp != null) - temp(this, EventArgs.Empty); - } + if (disposing) + Disposed.DispatchEvent(this, EventArgs.Empty); + } + [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")] public void Dispose() { if (Interlocked.Increment(ref m_disposed) == 1) { Dispose(true);
--- a/Implab/Components/ExecutionState.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Components/ExecutionState.cs Tue Aug 22 12:45:01 2017 +0300 @@ -13,6 +13,12 @@ Running, + Suspending, + + Suspended, + + Resuming, + Stopping, Failed,
--- a/Implab/Components/IInitializable.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Components/IInitializable.cs Tue Aug 22 12:45:01 2017 +0300 @@ -3,8 +3,8 @@ namespace Implab.Components { /// <summary> /// Initializable components are created and initialized in two steps, first we have create the component, - /// then we have to complete it's creation by calling an <see cref="Init()"/> method. All parameters needed - /// to complete the initialization must be passed before the calling <see cref="Init()"/> + /// then we have to complete it's creation by calling an <see cref="Initialize()"/> method. All parameters needed + /// to complete the initialization must be passed before the calling <see cref="Initialize()"/> /// </summary> public interface IInitializable { /// <summary> @@ -12,10 +12,10 @@ /// </summary> /// <remarks> /// Normally virtual methods shouldn't be called from the constructor, due to the incomplete object state, but - /// they can be called from this method. This method is also usefull when we constructing a complex grpah + /// they can be called from this method. This method is aьуерщlso usefull when we constructing a complex grpah /// of components where cyclic references may take place. /// </remarks> - void Init(); + void Initialize(); } }
--- a/Implab/Components/IRunnable.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Components/IRunnable.cs Tue Aug 22 12:45:01 2017 +0300 @@ -1,13 +1,29 @@ using System; namespace Implab.Components { - public interface IRunnable { + /// <summary> + /// Interface for the component which performs a long running task. + /// </summary> + /// <remarks> + /// <para>The component also should implement <see cref="IDisposable"/> interface to be able to release used resources.</para> + /// <para>All methods of this interface must be a thread safe. If the operation is not applicable in the current state the + /// method should throw an exception and keep the current state unchanged.</para> + /// </remarks> + public interface IRunnable : IDisposable { + /// <summary> + /// Starts this instance + /// </summary> IPromise Start(); + /// <summary> + /// Stops this instance, after the instance is stopped it can move to Failed, Ready or Disposed state, in case with the last it can't be reused. + /// </summary> IPromise Stop(); ExecutionState State { get; } + event EventHandler<StateChangeEventArgs> StateChanged; + Exception LastError { get; } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Components/PollingComponent.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,155 @@ +using System; +using System.Threading; +using Implab.Diagnostics; + +namespace Implab.Components { + public class PollingComponent : RunnableComponent { + readonly Timer m_timer; + readonly Func<Func<ICancellationToken, IPromise>, IPromise> m_dispatcher; + readonly TimeSpan m_interval; + + readonly object m_lock = new object(); + + ActionTask m_pending; + + protected PollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(initialized) { + m_timer = new Timer(OnInternalTick); + + m_interval = interval; + m_dispatcher = dispatcher; + } + + protected override IPromise OnStart() { + m_timer.Change(TimeSpan.Zero, m_interval); + + return base.OnStart(); + } + + void OnInternalTick(object state) { + if (StartTick()) { + try { + if (m_dispatcher != null) { + var result = m_dispatcher(OnTick); + m_pending.CancellationRequested(result.Cancel); + AwaitTick(result); + } else { + AwaitTick(OnTick(m_pending)); + } + } catch (Exception error) { + HandleTickError(error); + } + } + } + + /// <summary> + /// Checks wheather there is no running handler in the component and marks that the handler is starting. + /// </summary> + /// <returns>boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running.</returns> + /// <remarks> + /// If the component is stopping no new handlers can be run. Every successful call to this method must be completed with either AwaitTick or HandleTickError handlers. + /// </remarks> + protected virtual bool StartTick() { + lock (m_lock) { + if (State != ExecutionState.Running || m_pending != null) + return false; + // actually the component may be in a not running state here (stopping, disposed or what ever), + // but OnStop method also locks on the same object and will handle operation in m_pending + m_pending = new ActionTask( + () => { + // only one operation is running, it's safe to assing m_pending from it + m_pending = null; + }, + ex => { + try { + OnTickError(ex); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } finally { + m_pending = null; + } + // suppress error + }, + ex => { + try { + OnTickCancel(ex); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } finally { + m_pending = null; + } + // supress cancellation + }, + false + ); + return true; + } + } + + /// <summary> + /// Awaits the tick. + /// </summary> + /// <param name="tick">Tick.</param> + /// <remarks> + /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. + /// </remarks> + void AwaitTick(IPromise tick) { + if (tick == null) { + m_pending.Resolve(); + } else { + tick.On( + m_pending.Resolve, + m_pending.Reject, + m_pending.CancelOperation + ); + } + } + + /// <summary> + /// Handles the tick error. + /// </summary> + /// <remarks> + /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. + /// </remarks> + void HandleTickError(Exception error) { + m_pending.Reject(error); + } + + protected virtual void OnTickError(Exception error) { + } + + protected virtual void OnTickCancel(Exception error) { + } + + /// <summary> + /// Invoked when the timer ticks, use this method to implement your logic + /// </summary> + protected virtual IPromise OnTick(ICancellationToken cancellationToken) { + return Promise.Success; + } + + protected override IPromise OnStop() { + m_timer.Change(-1, -1); + + // the component is in the stopping state + lock (m_lock) { + // after this lock no more pending operations could be created + var pending = m_pending; + // m_pending could be fulfilled and set to null already + if (pending != null) { + pending.Cancel(); + return pending.Then(base.OnStop); + } + } + + return base.OnStop(); + } + + protected override void Dispose(bool disposing) { + if (disposing) + m_timer.Dispose(); + + base.Dispose(disposing); + } + } +} +
--- a/Implab/Components/RunnableComponent.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Components/RunnableComponent.cs Tue Aug 22 12:45:01 2017 +0300 @@ -1,5 +1,6 @@ using System; - +using System.Diagnostics.CodeAnalysis; + namespace Implab.Components { public abstract class RunnableComponent : IDisposable, IRunnable, IInitializable { enum Commands { @@ -9,54 +10,95 @@ Start, Stop, Dispose, - Last = Dispose + Reset, + Last = Reset } class StateMachine { - static readonly ExecutionState[,] _transitions; + public static readonly ExecutionState[,] ReusableTransitions; + public static readonly ExecutionState[,] NonreusableTransitions; + + class StateBuilder { + readonly ExecutionState[,] m_states; + + public ExecutionState[,] States { + get { return m_states; } + } + public StateBuilder(ExecutionState[,] states) { + m_states = states; + } + + public StateBuilder() { + m_states = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1]; + } + + public StateBuilder Edge(ExecutionState s1, ExecutionState s2, Commands cmd) { + m_states[(int)s1, (int)cmd] = s2; + return this; + } + + public StateBuilder Clone() { + return new StateBuilder((ExecutionState[,])m_states.Clone()); + } + } static StateMachine() { - _transitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1]; + ReusableTransitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1]; - Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init); - Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose); + var common = new StateBuilder() + .Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init) + .Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose) - Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok); - Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail); + .Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok) + .Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail) - Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start); - Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose); + .Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start) + .Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose) + + .Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok) + .Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail) + .Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop) + .Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose) - Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok); - Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail); - Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop); - Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose); + .Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail) + .Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop) + .Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose) + + .Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose) + .Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset) + + .Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail) + .Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose) + + .Edge(ExecutionState.Disposed, ExecutionState.Disposed, Commands.Dispose); - Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail); - Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop); - Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose); + var reusable = common + .Clone() + .Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok); + + var nonreusable = common + .Clone() + .Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok); + + NonreusableTransitions = nonreusable.States; + ReusableTransitions = reusable.States; - Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail); - Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok); - - Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose); } - static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) { - _transitions[(int)s1, (int)cmd] = s2; - } + readonly ExecutionState[,] m_states; public ExecutionState State { get; private set; } - public StateMachine(ExecutionState initial) { + public StateMachine(ExecutionState[,] states, ExecutionState initial) { State = initial; + m_states = states; } public bool Move(Commands cmd) { - var next = _transitions[(int)State, (int)cmd]; + var next = m_states[(int)State, (int)cmd]; if (next == ExecutionState.Undefined) return false; State = next; @@ -67,46 +109,167 @@ IPromise m_pending; Exception m_lastError; - readonly StateMachine m_stateMachine; - - protected RunnableComponent(bool initialized) { - m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created); - } - - protected virtual int DisposeTimeout { - get { - return 10000; - } + readonly StateMachine m_stateMachine; + readonly bool m_reusable; + public event EventHandler<StateChangeEventArgs> StateChanged; + + /// <summary> + /// Initializes component state. + /// </summary> + /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param> + /// <param name="reusable">If set, the component may start after it has been stopped, otherwise the component is disposed after being stopped.</param> + protected RunnableComponent(bool initialized, bool reusable) { + m_stateMachine = new StateMachine( + reusable ? StateMachine.ReusableTransitions : StateMachine.NonreusableTransitions, + initialized ? ExecutionState.Ready : ExecutionState.Created + ); + m_reusable = reusable; + } + + /// <summary> + /// Initializes component state. The component created with this constructor is not reusable, i.e. it will be disposed after stop. + /// </summary> + /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param> + protected RunnableComponent(bool initialized) : this(initialized, false) { } void ThrowInvalidCommand(Commands cmd) { if (m_stateMachine.State == ExecutionState.Disposed) - throw new ObjectDisposedException(ToString()); - - throw new InvalidOperationException(String.Format("Commnd {0} is not allowed in the state {1}", cmd, m_stateMachine.State)); + throw new ObjectDisposedException(ToString()); + + throw new InvalidOperationException(String.Format("Command {0} is not allowed in the state {1}", cmd, m_stateMachine.State)); + } + + bool MoveIfInState(Commands cmd, IPromise pending, Exception error, ExecutionState state) { + ExecutionState prev, current; + lock (m_stateMachine) { + if (m_stateMachine.State != state) + return false; + + prev = m_stateMachine.State; + if (!m_stateMachine.Move(cmd)) + ThrowInvalidCommand(cmd); + current = m_stateMachine.State; + + m_pending = pending; + m_lastError = error; + } + if (prev != current) + OnStateChanged(prev, current, error); + return true; } - void Move(Commands cmd) { - if (!m_stateMachine.Move(cmd)) - ThrowInvalidCommand(cmd); + bool MoveIfPending(Commands cmd, IPromise pending, Exception error, IPromise expected) { + ExecutionState prev, current; + lock (m_stateMachine) { + if (m_pending != expected) + return false; + prev = m_stateMachine.State; + if (!m_stateMachine.Move(cmd)) + ThrowInvalidCommand(cmd); + current = m_stateMachine.State; + m_pending = pending; + m_lastError = error; + } + if (prev != current) + OnStateChanged(prev, current, error); + return true; + } + + IPromise Move(Commands cmd, IPromise pending, Exception error) { + ExecutionState prev, current; + IPromise ret; + lock (m_stateMachine) { + prev = m_stateMachine.State; + if (!m_stateMachine.Move(cmd)) + ThrowInvalidCommand(cmd); + current = m_stateMachine.State; + + ret = m_pending; + m_pending = pending; + m_lastError = error; + + } + if (prev != current) + OnStateChanged(prev, current, error); + return ret; } - void Invoke(Commands cmd, Action action) { - lock (m_stateMachine) - Move(cmd); - + /// <summary> + /// Handles the state of the component change event, raises the <see cref="StateChanged"/> event, handles + /// the transition to the <see cref="ExecutionState.Disposed"/> state (calls <see cref="Dispose(bool)"/> method). + /// </summary> + /// <param name="previous">The previous state</param> + /// <param name="current">The current state</param> + /// <param name="error">The last error if any.</param> + /// <remarks> + /// <para> + /// If the previous state and the current state are same this method isn't called, such situiation is treated + /// as the component hasn't changed it's state. + /// </para> + /// <para> + /// When overriding this method ensure the call is made to the base implementation, otherwise it will lead to + /// the wrong behavior of the component. + /// </para> + /// </remarks> + protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) { + StateChanged.DispatchEvent( + this, + new StateChangeEventArgs { + State = current, + LastError = error + } + ); + if (current == ExecutionState.Disposed) { + GC.SuppressFinalize(this); + Dispose(true); + } + } + + /// <summary> + /// Moves the component from running to failed state. + /// </summary> + /// <param name="error">The exception which is describing the error.</param> + protected bool Fail(Exception error) { + return MoveIfInState(Commands.Fail, null, error, ExecutionState.Running); + } + + /// <summary> + /// Tries to reset <see cref="ExecutionState.Failed"/> state to <see cref="ExecutionState.Ready"/>. + /// </summary> + /// <returns>True if component is reset to <see cref="ExecutionState.Ready"/>, false if the componet wasn't + /// in <see cref="ExecutionState.Failed"/> state.</returns> + /// <remarks> + /// This method checks the current state of the component and if it's in <see cref="ExecutionState.Failed"/> + /// moves component to <see cref="ExecutionState.Initializing"/>. + /// The <see cref="OnResetState()"/> is called and if this method completes succesfully the component moved + /// to <see cref="ExecutionState.Ready"/> state, otherwise the component is moved to <see cref="ExecutionState.Failed"/> + /// state. If <see cref="OnResetState()"/> throws an exception it will be propagated by this method to the caller. + /// </remarks> + protected bool ResetState() { + if (!MoveIfInState(Commands.Reset, null, null, ExecutionState.Failed)) + return false; + try { - action(); - lock(m_stateMachine) - Move(Commands.Ok); - + OnResetState(); + Move(Commands.Ok, null, null); + return true; } catch (Exception err) { - lock (m_stateMachine) { - Move(Commands.Fail); - m_lastError = err; - } + Move(Commands.Fail, null, err); throw; - } + } + } + + /// <summary> + /// This method is called by <see cref="ResetState"/> to reinitialize component in the failed state. + /// </summary> + /// <remarks> + /// Default implementation throws <see cref="NotImplementedException"/> which will cause the component + /// fail to reset it's state and it left in <see cref="ExecutionState.Failed"/> state. + /// If this method doesn't throw exceptions the component is moved to <see cref="ExecutionState.Ready"/> state. + /// </remarks> + protected virtual void OnResetState() { + throw new NotImplementedException(); } IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) { @@ -115,40 +278,20 @@ var task = new ActionChainTask(action, null, null, true); - lock (m_stateMachine) { - Move(cmd); - - prev = m_pending; + Action<Exception> errorOrCancel = e => { + if (e == null) + e = new OperationCanceledException(); + MoveIfPending(Commands.Fail, null, e, promise); + throw new PromiseTransientException(e); + }; - Action<Exception> errorOrCancel = e => { - if (e == null) - e = new OperationCanceledException(); - - lock (m_stateMachine) { - if (m_pending == promise) { - Move(Commands.Fail); - m_pending = null; - m_lastError = e; - } - } - throw new PromiseTransientException(e); - }; + promise = task.Then( + () => MoveIfPending(Commands.Ok, null, null, promise), + errorOrCancel, + errorOrCancel + ); - promise = task.Then( - () => { - lock(m_stateMachine) { - if (m_pending == promise) { - Move(Commands.Ok); - m_pending = null; - } - } - }, - errorOrCancel, - errorOrCancel - ); - - m_pending = promise; - } + prev = Move(cmd, promise, null); if (prev == null) task.Resolve(); @@ -161,8 +304,16 @@ #region IInitializable implementation - public void Init() { - Invoke(Commands.Init, OnInitialize); + public void Initialize() { + Move(Commands.Init, null, null); + + try { + OnInitialize(); + Move(Commands.Ok, null, null); + } catch (Exception err) { + Move(Commands.Fail, null, err); + throw; + } } protected virtual void OnInitialize() { @@ -177,15 +328,15 @@ } protected virtual IPromise OnStart() { - return Promise.SUCCESS; + return Promise.Success; } public IPromise Stop() { - return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose); + return InvokeAsync(Commands.Stop, OnStop, StopPending); } protected virtual IPromise OnStop() { - return Promise.SUCCESS; + return Promise.Success; } /// <summary> @@ -224,39 +375,35 @@ #region IDisposable implementation + /// <summary> + /// Releases all resource used by the <see cref="Implab.Components.RunnableComponent"/> object. + /// </summary> + /// <remarks> + /// <para>Will not try to stop the component, it will just release all resources. + /// To cleanup the component gracefully use <see cref="Stop()"/> method.</para> + /// <para> + /// In normal cases the <see cref="Dispose()"/> method shouldn't be called, the call to the <see cref="Stop()"/> + /// method is sufficient to cleanup the component. Call <see cref="Dispose()"/> only to cleanup after errors, + /// especially if <see cref="Stop"/> method is failed. Using this method insted of <see cref="Stop()"/> may + /// lead to the data loss by the component. + /// </para></remarks> + [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")] public void Dispose() { - IPromise pending; - lock (m_stateMachine) { - if (m_stateMachine.State == ExecutionState.Disposed) - return; - - Move(Commands.Dispose); - - GC.SuppressFinalize(this); - - pending = m_pending; - m_pending = null; - } - if (pending != null) { - pending.Cancel(); - pending.Timeout(DisposeTimeout).On( - () => Dispose(true, null), - err => Dispose(true, err), - reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason)) - ); - } else { - Dispose(true, m_lastError); - } + Move(Commands.Dispose, null, null); } ~RunnableComponent() { - Dispose(false, null); + Dispose(false); } #endregion - protected virtual void Dispose(bool disposing, Exception lastError) { - + /// <summary> + /// Releases all resources used by the component, called automatically, override this method to implement your cleanup. + /// </summary> + /// <param name="disposing">true if this method is called during normal dispose process.</param> + /// <param name="pending">The operation which is currenty pending</param> + protected virtual void Dispose(bool disposing) { } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Components/StateChangeEventArgs.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,16 @@ +using System; + +namespace Implab.Components +{ + public class StateChangeEventArgs : EventArgs { + /// <summary> + /// The error information if any + /// </summary> + public Exception LastError { get; set; } + + /// <summary> + /// The state of the service corresponding to this event + /// </summary> + public ExecutionState State { get; set; } + } +}
--- a/Implab/Diagnostics/EventText.cs Tue Aug 22 09:35:54 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Implab.Diagnostics { - public struct EventText { - public int indent; - - public string content; - } -}
--- a/Implab/Diagnostics/ListenerBase.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/ListenerBase.cs Tue Aug 22 12:45:01 2017 +0300 @@ -59,8 +59,8 @@ public void UnsubscribeAll() { lock (m_subscriptions) { - foreach (var subscription in m_subscriptions.Values) - subscription(); + foreach (var remove in m_subscriptions.Values) + remove(); m_subscriptions.Clear(); } }
--- a/Implab/Diagnostics/LogChannel.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/LogChannel.cs Tue Aug 22 12:45:01 2017 +0300 @@ -69,7 +69,7 @@ this, new LogEventArgs<TEvent>( data, - Name, + this, traceContext.ThreadId, traceContext.CurrentOperation, traceContext.CurrentOperation.Duration @@ -77,5 +77,9 @@ ); } } + + public override string ToString() { + return Name; + } } }
--- a/Implab/Diagnostics/LogEventArgs.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/LogEventArgs.cs Tue Aug 22 12:45:01 2017 +0300 @@ -2,7 +2,7 @@ namespace Implab.Diagnostics { public class LogEventArgs : EventArgs { - public string ChannelName { + public object Channel { get; private set; } @@ -18,8 +18,8 @@ get; private set; } - public LogEventArgs(string channelName, int threadId, LogicalOperation operation, int timeOffset) { - ChannelName = channelName; + public LogEventArgs(object channel, int threadId, LogicalOperation operation, int timeOffset) { + Channel = channel; ThreadId = threadId; Operation = operation; OperationTimeOffset = timeOffset;
--- a/Implab/Diagnostics/LogEventArgsT.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/LogEventArgsT.cs Tue Aug 22 12:45:01 2017 +0300 @@ -5,7 +5,7 @@ private set; } - public LogEventArgs(TEvent value,string channelName, int threadId, LogicalOperation operation, int timeOffset) : base(channelName, threadId, operation, timeOffset) { + public LogEventArgs(TEvent value,object channel, int threadId, LogicalOperation operation, int timeOffset) : base(channel, threadId, operation, timeOffset) { Value = value; } }
--- a/Implab/Diagnostics/OperationContext.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/OperationContext.cs Tue Aug 22 12:45:01 2017 +0300 @@ -30,11 +30,11 @@ else if (m_ownership) m_current = LogicalOperation.EMPTY; else { - TraceLog.TraceWarning("DetachLogicalOperation can't be applied in the current context"); + TraceLog.TraceWarning("DetachLogicalOperation can't be performed in the current context"); detached = LogicalOperation.EMPTY; } } else { - TraceLog.TraceWarning("DetachLogicalOperation can't be applied in the current context"); + TraceLog.TraceWarning("DetachLogicalOperation can't be performed in the current context"); } return detached; @@ -50,7 +50,7 @@ m_ownership = false; } } else { - TraceLog.TraceWarning("EndLogicalOperation can't be applied in the current context"); + TraceLog.TraceWarning("EndLogicalOperation can't be performed in the current context"); } return current; }
--- a/Implab/Diagnostics/TextFileListener.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/TextFileListener.cs Tue Aug 22 12:45:01 2017 +0300 @@ -5,6 +5,7 @@ namespace Implab.Diagnostics { public class TextFileListener: ListenerBase { readonly TextWriter m_textWriter; + readonly object m_lock = new object(); public TextFileListener(string fileName) { m_textWriter = File.CreateText(fileName); @@ -18,9 +19,9 @@ var msg = new StringBuilder(); for (int i = 0; i < args.Operation.Level; i++) msg.Append(" "); - msg.AppendFormat("[{0}]:{1}: {2}", args.ThreadId, args.ChannelName, entry); + msg.AppendFormat("[{0}]+{3}ms:{1}: {2}", args.ThreadId, args.Channel, entry, args.OperationTimeOffset); - lock (m_textWriter) { + lock (m_lock) { if (!IsDisposed) { // тут гарантировано еще не освобожден m_textWriter m_textWriter.WriteLine(msg); @@ -35,7 +36,7 @@ base.Dispose(disposing); if (disposing) { // IsDisposed = true - lock (m_textWriter) { + lock (m_lock) { Safe.Dispose(m_textWriter); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Diagnostics/Trace.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Implab.Diagnostics { + public static class Trace<T> { + + readonly static LogChannel<TraceEvent> _channel = new LogChannel<TraceEvent>(typeof(T).Name); + + public static LogChannel<TraceEvent> Channel { + get { return _channel; } + } + + /// <summary> + /// Starts the logical operation nested to the current operation nested to the current one. + /// </summary> + [Conditional("TRACE")] + public static void StartLogicalOperation() { + TraceContext.Instance.StartLogicalOperation(); + + } + + /// <summary> + /// Starts the logical operation with the specified name, this name is usefull in logs. + /// </summary> + /// <param name="name">Name.</param> + [Conditional("TRACE")] + public static void StartLogicalOperation(string name) { + Channel.LogEvent(new TraceEvent(TraceContext.Instance.CurrentOperation, TraceEventType.OperationStarted, name)); + TraceContext.Instance.StartLogicalOperation(name); + } + + /// <summary> + /// Ends the logical operation and restores the previous one. + /// </summary> + [Conditional("TRACE")] + public static void EndLogicalOperation() { + var op = TraceContext.Instance.EndLogicalOperation(); + Channel.LogEvent(new TraceEvent(op, TraceEventType.OperationCompleted, String.Format("-{0} : {1}ms", op.Name, op.Duration))); + } + + /// <summary> + /// Writes an informational message. + /// </summary> + /// <param name="format">Format.</param> + /// <param name="arguments">Arguments.</param> + [Conditional("TRACE")] + public static void Log(string format, params object[] arguments) { + Channel.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Information, format, arguments)); + } + + /// <summary> + /// Writes a warning message. + /// </summary> + /// <param name="format">Format.</param> + /// <param name="arguments">Arguments.</param> + [Conditional("TRACE")] + public static void Warn(string format, params object[] arguments) { + Channel.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Warning, format, arguments)); + } + + [Conditional("TRACE")] + public static void Error(string format, params object[] arguments) { + Channel.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Error, format, arguments)); + } + + [Conditional("TRACE")] + public static void Error(Exception err) { + Error("{0}", err); + } + } +}
--- a/Implab/Diagnostics/TraceContext.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/TraceContext.cs Tue Aug 22 12:45:01 2017 +0300 @@ -48,7 +48,7 @@ } public void StartLogicalOperation(string name) { - LogChannel<TraceEvent>.Default.LogEvent(new TraceEvent(TraceEventType.OperationStarted, name)); + //LogChannel<TraceEvent>.Default.LogEvent(new TraceEvent(TraceContext.Instance.CurrentOperation, TraceEventType.OperationStarted, name)); m_current.BeginLogicalOperation(name); } @@ -56,9 +56,8 @@ StartLogicalOperation(String.Empty); } - public void EndLogicalOperation() { - var op = m_current.EndLogicalOperation(); - LogChannel<TraceEvent>.Default.LogEvent(new TraceEvent(TraceEventType.OperationCompleted, String.Format("-{0} : {1}ms",op.Name, op.Duration))); + public LogicalOperation EndLogicalOperation() { + return m_current.EndLogicalOperation(); } public LogicalOperation DetachLogicalOperation() {
--- a/Implab/Diagnostics/TraceEvent.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/TraceEvent.cs Tue Aug 22 12:45:01 2017 +0300 @@ -12,18 +12,36 @@ private set; } - public TraceEvent(TraceEventType type, string message) { + /// <summary> + /// The logical operation this event belongs to. + /// </summary> + public LogicalOperation Operation { + get; + private set; + } + + /// <summary> + /// Gets the time offset in milliseconds from the start of the operation, if the operation is not specified the value is zero. + /// </summary> + public int OperationTime { + get; + private set; + } + + public TraceEvent(LogicalOperation operation, TraceEventType type, string message) { EventType = type; Message = message; + Operation = operation; + if (operation != null) + OperationTime = operation.Duration; } public override string ToString() { - /*return EventType == TraceEventType.Information ? Message : String.Format("{0}: {1}", EventType, Message);*/ return Message; } - public static TraceEvent Create(TraceEventType type, string format, params object[] args) { - return new TraceEvent(type, format == null ? String.Empty : String.Format(format, args)); + public static TraceEvent Create(LogicalOperation operation, TraceEventType type, string format, params object[] args) { + return new TraceEvent(operation, type, format == null ? String.Empty : args == null || args.Length == 0 ? format : String.Format(format, args)); } } }
--- a/Implab/Diagnostics/TraceLog.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Diagnostics/TraceLog.cs Tue Aug 22 12:45:01 2017 +0300 @@ -7,39 +7,59 @@ namespace Implab.Diagnostics { /// <summary> - /// Класс для публикации событий выполнения программы, события публикуются через <see cref="LogChannel{TraceEvent}"/>. - /// Журнал трассировки отражает логический ход выполнения программы и существует всегда, поскольку тесно связан с - /// контекстом трассировки. + /// This class is used to trace a logical flow of the application, it publishes events to the default <see cref="TraceEvent"/> channel. /// </summary> public static class TraceLog { + /// <summary> + /// Starts the logical operation nested to the current operation nested to the current one. + /// </summary> [Conditional("TRACE")] public static void StartLogicalOperation() { TraceContext.Instance.StartLogicalOperation(); + } + /// <summary> + /// Starts the logical operation with the specified name, this name is usefull in logs. + /// </summary> + /// <param name="name">Name.</param> [Conditional("TRACE")] public static void StartLogicalOperation(string name) { TraceContext.Instance.StartLogicalOperation(name); } + /// <summary> + /// Ends the logical operation and restores the previous one. + /// </summary> [Conditional("TRACE")] public static void EndLogicalOperation() { - TraceContext.Instance.EndLogicalOperation(); + var op = TraceContext.Instance.EndLogicalOperation(); + LogChannel<TraceEvent>.Default.LogEvent(new TraceEvent(op, TraceEventType.OperationCompleted, String.Format("-{0} : {1}ms",op.Name, op.Duration))); } + /// <summary> + /// Writes an informational message. + /// </summary> + /// <param name="format">Format.</param> + /// <param name="arguments">Arguments.</param> [Conditional("TRACE")] public static void TraceInformation(string format, params object[] arguments) { - LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceEventType.Information, format, arguments)); + LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Information, format, arguments)); } + /// <summary> + /// Writes a warning message. + /// </summary> + /// <param name="format">Format.</param> + /// <param name="arguments">Arguments.</param> [Conditional("TRACE")] public static void TraceWarning(string format, params object[] arguments) { - LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceEventType.Warning, format, arguments)); + LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Warning, format, arguments)); } [Conditional("TRACE")] public static void TraceError(string format, params object[] arguments) { - LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceEventType.Error, format, arguments)); + LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Error, format, arguments)); } [Conditional("TRACE")]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Diagnostics/TraceSourceAttribute.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,10 @@ +using System; + +namespace Implab.Diagnostics { + /// <summary> + /// Used to mark class which uses <see cref="Trace{T}"/> class to trace it's events + /// </summary> + [AttributeUsage(AttributeTargets.Class)] + public class TraceSourceAttribute : Attribute { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/FailedPromise.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,100 @@ +using System; +using System.Reflection; + +namespace Implab { + public class FailedPromise : IPromise { + readonly Exception m_error; + public FailedPromise(Exception error) { + Safe.ArgumentNotNull(error, "error"); + m_error = error; + } + + #region IPromise implementation + + public IPromise On(Action success, Action<Exception> error, Action<Exception> cancel) { + if (error != null) { + try { + error(m_error); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise On(Action success, Action<Exception> error) { + if (error != null) { + try { + error(m_error); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise On(Action success) { + return this; + } + + public IPromise On(Action handler, PromiseEventType events) { + if ((events & PromiseEventType.Error) != 0) { + try { + handler(); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise<T> Cast<T>() { + return (IPromise<T>)this; + } + + public void Join() { + throw new TargetInvocationException(Error); + } + + public void Join(int timeout) { + throw new TargetInvocationException(Error); + } + + public virtual Type PromiseType { + get { + return typeof(void); + } + } + + public bool IsResolved { + get { + return true; + } + } + + public bool IsCancelled { + get { + return false; + } + } + + public Exception Error { + get { + return m_error; + } + } + + #endregion + + #region ICancellable implementation + + public void Cancel() { + } + + public void Cancel(Exception reason) { + } + + #endregion + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/FailedPromiseT.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,65 @@ +using System; +using System.Reflection; + +namespace Implab { + public class FailedPromise<T> : FailedPromise, IPromise<T> { + public FailedPromise(Exception error) : base(error) { + } + + public IPromise<T> On(Action<T> success, Action<Exception> error, Action<Exception> cancel) { + if (error != null) { + try { + error(Error); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise<T> On(Action<T> success, Action<Exception> error) { + if (error != null) { + try { + error(Error); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise<T> On(Action<T> success) { + return this; + } + + T IPromise<T>.Join() { + throw new TargetInvocationException(Error); + } + + T IPromise<T>.Join(int timeout) { + throw new TargetInvocationException(Error); + } + + + IPromise<T> IPromise<T>.On(Action success, Action<Exception> error, Action<Exception> cancel) { + On(success, error, cancel); + return this; + } + + IPromise<T> IPromise<T>.On(Action success, Action<Exception> error) { + On(success, error); + return this; + } + + IPromise<T> IPromise<T>.On(Action success) { + On(success); + return this; + } + + IPromise<T> IPromise<T>.On(Action handler, PromiseEventType events) { + On(handler, events); + return this; + } + } +} +
--- a/Implab/Formats/JSON/JSONParser.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Formats/JSON/JSONParser.cs Tue Aug 22 12:45:01 2017 +0300 @@ -274,7 +274,7 @@ protected override void Dispose(bool disposing) { if (disposing) - Safe.Dispose(m_scanner); + m_scanner.Dispose(); } /// <summary>
--- a/Implab/Formats/JSON/JSONScanner.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Formats/JSON/JSONScanner.cs Tue Aug 22 12:45:01 2017 +0300 @@ -102,7 +102,7 @@ protected override void Dispose(bool disposing) { if (disposing) - Safe.Dispose(m_scanner); + m_scanner.Dispose(); base.Dispose(disposing); } }
--- a/Implab/Implab.csproj Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Implab.csproj Tue Aug 22 12:45:01 2017 +0300 @@ -8,8 +8,6 @@ <RootNamespace>Implab</RootNamespace> <AssemblyName>Implab</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <ProductVersion>8.0.30703</ProductVersion> - <SchemaVersion>2.0</SchemaVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -74,15 +72,17 @@ <Reference Include="mscorlib" /> </ItemGroup> <ItemGroup> + <Compile Include="Components\StateChangeEventArgs.cs" /> <Compile Include="CustomEqualityComparer.cs" /> <Compile Include="Diagnostics\ConsoleTraceListener.cs" /> - <Compile Include="Diagnostics\EventText.cs" /> <Compile Include="Diagnostics\LogChannel.cs" /> <Compile Include="Diagnostics\LogicalOperation.cs" /> <Compile Include="Diagnostics\TextFileListener.cs" /> + <Compile Include="Diagnostics\Trace.cs" /> <Compile Include="Diagnostics\TraceLog.cs" /> <Compile Include="Diagnostics\TraceEvent.cs" /> <Compile Include="Diagnostics\TraceEventType.cs" /> + <Compile Include="Diagnostics\TraceSourceAttribute.cs" /> <Compile Include="ICancellable.cs" /> <Compile Include="IProgressHandler.cs" /> <Compile Include="IProgressNotifier.cs" /> @@ -98,9 +98,10 @@ <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Parallels\AsyncPool.cs" /> <Compile Include="Safe.cs" /> + <Compile Include="SyncContextPromise.cs" /> <Compile Include="ValueEventArgs.cs" /> <Compile Include="PromiseExtensions.cs" /> - <Compile Include="SyncContextPromise.cs" /> + <Compile Include="SyncContextPromiseT.cs" /> <Compile Include="Diagnostics\OperationContext.cs" /> <Compile Include="Diagnostics\TraceContext.cs" /> <Compile Include="Diagnostics\LogEventArgs.cs" /> @@ -194,6 +195,9 @@ <Compile Include="Components\LazyAndWeak.cs" /> <Compile Include="AbstractTask.cs" /> <Compile Include="AbstractTaskT.cs" /> + <Compile Include="FailedPromise.cs" /> + <Compile Include="FailedPromiseT.cs" /> + <Compile Include="Components\PollingComponent.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup /> @@ -201,14 +205,12 @@ <MonoDevelop> <Properties> <Policies> - <CSharpFormattingPolicy IndentBlock="True" IndentBraces="False" IndentSwitchSection="False" IndentSwitchCaseSection="True" LabelPositioning="OneLess" NewLinesForBracesInTypes="True" NewLinesForBracesInMethods="True" NewLinesForBracesInProperties="False" NewLinesForBracesInAccessors="False" NewLinesForBracesInAnonymousMethods="False" NewLinesForBracesInControlBlocks="False" NewLinesForBracesInAnonymousTypes="False" NewLinesForBracesInObjectCollectionArrayInitializers="False" NewLinesForBracesInLambdaExpressionBody="False" NewLineForElse="False" NewLineForCatch="False" NewLineForFinally="False" NewLineForMembersInObjectInit="False" NewLineForMembersInAnonymousTypes="False" NewLineForClausesInQuery="False" SpacingAfterMethodDeclarationName="True" SpaceWithinMethodDeclarationParenthesis="False" SpaceBetweenEmptyMethodDeclarationParentheses="False" SpaceAfterMethodCallName="True" SpaceWithinMethodCallParentheses="False" SpaceBetweenEmptyMethodCallParentheses="False" SpaceAfterControlFlowStatementKeyword="True" SpaceWithinExpressionParentheses="False" SpaceWithinCastParentheses="False" SpaceWithinOtherParentheses="False" SpaceAfterCast="False" SpacesIgnoreAroundVariableDeclaration="False" SpaceBeforeOpenSquareBracket="True" SpaceBetweenEmptySquareBrackets="False" SpaceWithinSquareBrackets="False" SpaceAfterColonInBaseTypeDeclaration="True" SpaceAfterComma="True" SpaceAfterDot="False" SpaceAfterSemicolonsInForStatement="True" SpaceBeforeColonInBaseTypeDeclaration="True" SpaceBeforeComma="False" SpaceBeforeDot="False" SpaceBeforeSemicolonsInForStatement="False" SpacingAroundBinaryOperator="Single" WrappingPreserveSingleLine="True" WrappingKeepStatementsOnSingleLine="True" PlaceSystemDirectiveFirst="True" scope="text/x-csharp" /> - <TextStylePolicy FileWidth="120" TabWidth="4" IndentWidth="4" RemoveTrailingWhitespace="True" NoTabsAfterNonTabs="False" TabsToSpaces="True" EolMarker="Unix" scope="text/x-csharp" /> + <CSharpFormattingPolicy IndentSwitchBody="True" NamespaceBraceStyle="EndOfLine" ClassBraceStyle="EndOfLine" InterfaceBraceStyle="EndOfLine" StructBraceStyle="EndOfLine" EnumBraceStyle="EndOfLine" MethodBraceStyle="EndOfLine" ConstructorBraceStyle="EndOfLine" DestructorBraceStyle="EndOfLine" BeforeMethodDeclarationParentheses="False" BeforeMethodCallParentheses="False" BeforeConstructorDeclarationParentheses="False" NewLineBeforeConstructorInitializerColon="NewLine" NewLineAfterConstructorInitializerColon="SameLine" BeforeIndexerDeclarationBracket="False" BeforeDelegateDeclarationParentheses="False" NewParentheses="False" SpacesBeforeBrackets="False" inheritsSet="Mono" inheritsScope="text/x-csharp" scope="text/x-csharp" /> + <TextStylePolicy FileWidth="120" EolMarker="Unix" inheritsSet="VisualStudio" inheritsScope="text/plain" scope="text/x-csharp" /> <DotNetNamingPolicy DirectoryNamespaceAssociation="PrefixedHierarchical" ResourceNamePolicy="MSBuild" /> - <TextStylePolicy FileWidth="120" TabWidth="4" TabsToSpaces="False" IndentWidth="4" RemoveTrailingWhitespace="True" NoTabsAfterNonTabs="False" EolMarker="Native" scope="application/xml" /> - <XmlFormattingPolicy scope="application/xml"> - <DefaultFormat OmitXmlDeclaration="False" NewLineChars="
" IndentContent="True" ContentIndentString="	" AttributesInNewLine="False" MaxAttributesPerLine="10" AttributesIndentString="	" WrapAttributes="False" AlignAttributes="False" AlignAttributeValues="False" QuoteChar=""" SpacesBeforeAssignment="0" SpacesAfterAssignment="0" EmptyLinesBeforeStart="0" EmptyLinesAfterStart="0" EmptyLinesBeforeEnd="0" EmptyLinesAfterEnd="0" /> - </XmlFormattingPolicy> - <TextStylePolicy FileWidth="120" TabWidth="4" TabsToSpaces="False" IndentWidth="4" RemoveTrailingWhitespace="True" NoTabsAfterNonTabs="False" EolMarker="Native" scope="text/plain" /> + <TextStylePolicy FileWidth="120" TabsToSpaces="False" inheritsSet="VisualStudio" inheritsScope="text/plain" scope="application/xml" /> + <XmlFormattingPolicy inheritsSet="Mono" inheritsScope="application/xml" scope="application/xml" /> + <TextStylePolicy FileWidth="120" TabsToSpaces="False" inheritsSet="VisualStudio" inheritsScope="text/plain" scope="text/plain" /> <NameConventionPolicy> <Rules> <NamingRule Name="Namespaces" AffectedEntity="Namespace" VisibilityMask="VisibilityMask" NamingStyle="PascalCase" IncludeInstanceMembers="True" IncludeStaticEntities="True" /> @@ -268,10 +270,5 @@ </Properties> </MonoDevelop> </ProjectExtensions> - <ItemGroup> - <Folder Include="Components\" /> - <Folder Include="Automaton\RegularExpressions\" /> - <Folder Include="Formats\" /> - <Folder Include="Formats\JSON\" /> - </ItemGroup> + <ItemGroup /> </Project> \ No newline at end of file
--- a/Implab/Promise.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Promise.cs Tue Aug 22 12:45:01 2017 +0300 @@ -3,11 +3,10 @@ namespace Implab { public class Promise : AbstractPromise, IDeferred { - public static readonly Promise SUCCESS; + public static readonly IPromise Success; static Promise() { - SUCCESS = new Promise(); - SUCCESS.Resolve(); + Success = new SuccessPromise(); } public void Resolve() { @@ -16,7 +15,11 @@ public void Reject(Exception error) { SetError(error); - } + } + + public static IPromise FromException(Exception exception) { + return new FailedPromise(exception); + } } }
--- a/Implab/PromiseExtensions.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/PromiseExtensions.cs Tue Aug 22 12:45:01 2017 +0300 @@ -2,7 +2,8 @@ using System; using Implab.Diagnostics; using System.Collections.Generic; - +using System.Linq; + namespace Implab { public static class PromiseExtensions { public static IPromise<T> DispatchToCurrentContext<T>(this IPromise<T> that) { @@ -46,7 +47,7 @@ /// <param name="cleanup">Cleanup.</param> /// <typeparam name="TPromise">The 1st type parameter.</typeparam> /// <typeparam name="T">The 2nd type parameter.</typeparam> - public static TPromise EnsureDispatched<TPromise,T>(this TPromise that, IPromise<T> head, Action<T> cleanup) where TPromise : IPromise{ + public static TPromise EnsureDispatched<TPromise, T>(this TPromise that, IPromise<T> head, Action<T> cleanup) where TPromise : IPromise { Safe.ArgumentNotNull(that, "that"); Safe.ArgumentNotNull(head, "head"); @@ -55,12 +56,37 @@ return that; } - public static AsyncCallback AsyncCallback<T>(this Promise<T> that, Func<IAsyncResult,T> callback) { + /// <summary> + /// Adds a cancellation point to the chain of promises. When a cancellation request reaches the cancellation point the operation is + /// cancelled immediatelly, and the request is passed towards. If the operation at the higher level can not be cancelled is't result + /// will be collected with <paramref name="cleanup"/> callback. + /// </summary> + /// <typeparam name="T">The type of the promise result.</typeparam> + /// <param name="that">The promise to which the cancellation point should be attached.</param> + /// <param name="cleanup">The callback which is used to cleanup the result of the operation if the cancellation point is cancelled already.</param> + /// <returns>The promise</returns> + public static IPromise<T> CancellationPoint<T>(this IPromise<T> that, Action<T> cleanup) { + var meduim = new Promise<T>(); + + that.On(meduim.Resolve, meduim.Reject, meduim.CancelOperation); + + meduim.CancellationRequested(that.Cancel); + meduim.CancellationRequested(meduim.CancelOperation); + + if (cleanup != null) + meduim.On((Action<T>)null, null, (e) => { + that.On(cleanup); + }); + + return meduim; + } + + public static AsyncCallback AsyncCallback<T>(this Promise<T> that, Func<IAsyncResult, T> callback) { Safe.ArgumentNotNull(that, "that"); Safe.ArgumentNotNull(callback, "callback"); var op = TraceContext.Instance.CurrentOperation; return ar => { - TraceContext.Instance.EnterLogicalOperation(op,false); + TraceContext.Instance.EnterLogicalOperation(op, false); try { that.Resolve(callback(ar)); } catch (Exception err) { @@ -73,8 +99,8 @@ static void CancelByTimeoutCallback(object cookie) { ((ICancellable)cookie).Cancel(new TimeoutException()); - } - + } + /// <summary> /// Cancells promise after the specified timeout is elapsed. /// </summary> @@ -88,7 +114,21 @@ return that; } - public static IPromise Bundle(this ICollection<IPromise> that) { + public static IPromise PromiseAll(this IEnumerable<IPromise> that) { + Safe.ArgumentNotNull(that, "that"); + return PromiseAll(that.ToList()); + } + + public static IPromise<T[]> PromiseAll<T>(this IEnumerable<IPromise<T>> that) { + return PromiseAll(that, null); + } + + public static IPromise<T[]> PromiseAll<T>(this IEnumerable<IPromise<T>> that, Action<T> cleanup) { + Safe.ArgumentNotNull(that, "that"); + return PromiseAll(that.ToList(), cleanup); + } + + public static IPromise PromiseAll(this ICollection<IPromise> that) { Safe.ArgumentNotNull(that, "that"); int count = that.Count; @@ -101,7 +141,7 @@ } medium.On(() => { - foreach(var p2 in that) + foreach (var p2 in that) p2.Cancel(); }, PromiseEventType.ErrorOrCancel); @@ -128,17 +168,35 @@ return medium; } - public static IPromise<T[]> Bundle<T>(this ICollection<IPromise<T>> that) { - Safe.ArgumentNotNull(that, "that"); + public static IPromise<T[]> PromiseAll<T>(this ICollection<IPromise<T>> that) { + return PromiseAll(that, null); + } + /// <summary> + /// Creates a new promise which will be satisfied when all promises are satisfied. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="that"></param> + /// <param name="cleanup">A callback used to cleanup already resolved promises in case of an error</param> + /// <returns></returns> + public static IPromise<T[]> PromiseAll<T>(this ICollection<IPromise<T>> that, Action<T> cleanup) { + Safe.ArgumentNotNull(that, "that"); + int count = that.Count; + + if (count == 0) + return Promise<T[]>.FromResult(new T[0]); + int errors = 0; var medium = new Promise<T[]>(); var results = new T[that.Count]; medium.On(() => { - foreach(var p2 in that) - p2.Cancel(); + foreach (var p2 in that) { + p2.Cancel(); + if (cleanup != null) + p2.On(cleanup); + } }, PromiseEventType.ErrorOrCancel); int i = 0; @@ -205,12 +263,38 @@ public static IPromise<T2> Then<T, T2>(this IPromise<T> that, Func<T, T2> success, Func<Exception, T2> error, Func<Exception, T2> cancel) { Safe.ArgumentNotNull(that, "that"); - var d = new FuncTask<T,T2>(success, error, cancel, false); + Safe.ArgumentNotNull(success, "success"); + + var d = new FuncTask<T, T2>(success, error, cancel, false); that.On(d.Resolve, d.Reject, d.CancelOperation); d.CancellationRequested(that.Cancel); return d; } + public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success, Func<Exception, T> error, Func<Exception, T> cancel) { + Safe.ArgumentNotNull(that, "that"); + var d = new FuncTask<T, T>( + x => { + success(x); + return x; + }, + error, + cancel, + false + ); + that.On(d.Resolve, d.Reject, d.CancelOperation); + d.CancellationRequested(that.Cancel); + return d; + } + + public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success, Func<Exception, T> error) { + return Then(that, success, error, null); + } + + public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success) { + return Then(that, success, null, null); + } + public static IPromise<T2> Then<T, T2>(this IPromise<T> that, Func<T, T2> success, Func<Exception, T2> error) { return Then(that, success, error, null); } @@ -219,8 +303,79 @@ return Then(that, success, null, null); } + public static IPromise<T> Always<T>(this IPromise<T> that, Action handler) { + Func<Exception, T> errorOrCancel; + if (handler != null) + errorOrCancel = e => { + handler(); + throw new PromiseTransientException(e); + }; + else + errorOrCancel = null; + + return Then( + that, + x => { + handler(); + return x; + }, + errorOrCancel, + errorOrCancel); + } + + public static IPromise Always(this IPromise that, Action handler) { + Action<Exception> errorOrCancel; + if (handler != null) + errorOrCancel = e => { + handler(); + throw new PromiseTransientException(e); + }; + else + errorOrCancel = null; + + return Then( + that, + handler, + errorOrCancel, + errorOrCancel); + } + + public static IPromise Error(this IPromise that, Action<Exception> handler, bool handleCancellation) { + Action<Exception> errorOrCancel; + if (handler != null) + errorOrCancel = e => { + handler(e); + throw new PromiseTransientException(e); + }; + else + errorOrCancel = null; + + return Then(that, null, errorOrCancel, handleCancellation ? errorOrCancel : null); + } + + public static IPromise Error(this IPromise that, Action<Exception> handler) { + return Error(that, handler, false); + } + + public static IPromise<T> Error<T>(this IPromise<T> that, Action<Exception> handler, bool handleCancellation) { + Func<Exception, T> errorOrCancel; + if (handler != null) + errorOrCancel = e => { + handler(e); + throw new PromiseTransientException(e); + }; + else + errorOrCancel = null; + + return Then(that, null, errorOrCancel, handleCancellation ? errorOrCancel : null); + } + + public static IPromise<T> Error<T>(this IPromise<T> that, Action<Exception> handler) { + return Error(that, handler, false); + } + #region chain traits - public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception,IPromise> error, Func<Exception,IPromise> cancel) { + public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception, IPromise> error, Func<Exception, IPromise> cancel) { Safe.ArgumentNotNull(that, "that"); var d = new ActionChainTask(success, error, cancel, false); @@ -229,7 +384,7 @@ return d; } - public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception,IPromise> error) { + public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception, IPromise> error) { return Chain(that, success, error, null); } @@ -257,7 +412,7 @@ public static IPromise<T2> Chain<T, T2>(this IPromise<T> that, Func<T, IPromise<T2>> success, Func<Exception, IPromise<T2>> error, Func<Exception, IPromise<T2>> cancel) { Safe.ArgumentNotNull(that, "that"); - var d = new FuncChainTask<T,T2>(success, error, cancel, false); + var d = new FuncChainTask<T, T2>(success, error, cancel, false); that.On(d.Resolve, d.Reject, d.CancelOperation); if (success != null) d.CancellationRequested(that.Cancel); @@ -270,20 +425,51 @@ public static IPromise<T2> Chain<T, T2>(this IPromise<T> that, Func<T, IPromise<T2>> success) { return Chain(that, success, null, null); - } - + } + #endregion - - - #if NET_4_5 - + + public static IPromise<T2> Guard<T, T2>(this IPromise<T> that, Func<IPromise<T>, IPromise<T2>> continuation, Action<T> cleanup) { + Safe.ArgumentNotNull(that, "that"); + Safe.ArgumentNotNull(continuation, "continuation"); + return continuation(that).Error((err) => { + that.On(cleanup); + }, true); + } + +#if NET_4_5 + public static PromiseAwaiter<T> GetAwaiter<T>(this IPromise<T> that) { Safe.ArgumentNotNull(that, "that"); return new PromiseAwaiter<T>(that); - } + } + + public static PromiseAwaiter GetAwaiter(this IPromise that) { + Safe.ArgumentNotNull(that, "that"); - #endif + return new PromiseAwaiter(that); + } + + public static IPromise BoundCancellationToken(this IPromise that, CancellationToken ct) { + Safe.ArgumentNotNull(that, "that"); + ct.Register(that.Cancel); + return that.Then(null, null, (err) => { + ct.ThrowIfCancellationRequested(); + throw new PromiseTransientException(err); + }); + } + + public static IPromise<T> BoundCancellationToken<T>(this IPromise<T> that, CancellationToken ct) { + Safe.ArgumentNotNull(that, "that"); + ct.Register(that.Cancel); + return that.Then(null, null, (err) => { + ct.ThrowIfCancellationRequested(); + throw new PromiseTransientException(err); + }); + } + +#endif } }
--- a/Implab/PromiseT.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/PromiseT.cs Tue Aug 22 12:45:01 2017 +0300 @@ -45,9 +45,7 @@ } public static IPromise<T> FromException(Exception error) { - var p = new Promise<T>(); - p.Reject(error); - return p; + return new FailedPromise<T>(error); } public virtual void Resolve(T value) {
--- a/Implab/Safe.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/Safe.cs Tue Aug 22 12:45:01 2017 +0300 @@ -4,6 +4,11 @@ using System.Text; using System.Text.RegularExpressions; using System.Diagnostics; +using System.Collections; + +#if NET_4_5 +using System.Threading.Tasks; +#endif namespace Implab { @@ -60,62 +65,75 @@ } } - public static void Dispose(object obj) { - var d = obj as IDisposable; - if (d != null) - d.Dispose(); - } - - [DebuggerStepThrough] - public static IPromise<T> WrapPromise<T>(Func<T> action) { - ArgumentNotNull(action, "action"); - - var p = new Promise<T>(); - try { - p.Resolve(action()); - } catch (Exception err) { - p.Reject(err); - } - - return p; + public static void DisposeCollection(IEnumerable<IDisposable> objects) { + foreach (var d in objects) + Dispose(d); } - [DebuggerStepThrough] - public static IPromise WrapPromise(Action action) { - ArgumentNotNull(action, "action"); - - var p = new Promise(); - try { - action(); - p.Resolve(); - } catch (Exception err) { - p.Reject(err); - } - - return p; + public static void DisposeCollection(IEnumerable objects) { + foreach (var d in objects) + Dispose(d); } - [DebuggerStepThrough] - public static IPromise InvokePromise(Func<IPromise> action) { - ArgumentNotNull(action, "action"); - - try { - var p = action(); - if (p == null) { - var d = new Promise(); - d.Reject(new Exception("The action returned null")); - p = d; - } - return p; - } catch (Exception err) { - var p = new Promise(); - p.Reject(err); - return p; + public static void Dispose(object obj) { + if (obj is IDisposable) { + Dispose((IDisposable)obj); + } else if (obj is IEnumerable) { + DisposeCollection((IEnumerable)obj); } } [DebuggerStepThrough] - public static IPromise<T> InvokePromise<T>(Func<IPromise<T>> action) { + public static void DispatchEvent<T>(this EventHandler<T> handler, object sender, T args) { + if (handler != null) + handler(sender, args); + } + + [DebuggerStepThrough] + public static void DispatchEvent(this EventHandler handler, object sender, EventArgs args) { + if (handler != null) + handler(sender, args); + } + + [DebuggerStepThrough] + public static IPromise<T> Run<T>(Func<T> action) { + ArgumentNotNull(action, "action"); + + try { + return Promise<T>.FromResult(action()); + } catch (Exception err) { + return Promise<T>.FromException(err); + } + } + + [DebuggerStepThrough] + public static IPromise Run(Action action) { + ArgumentNotNull(action, "action"); + + try { + action(); + return Promise.Success; + } catch (Exception err) { + return new FailedPromise(err); + } + } + + [DebuggerStepThrough] + public static IPromise Run(Func<IPromise> action) { + ArgumentNotNull(action, "action"); + + try { + return action() ?? new FailedPromise(new Exception("The action returned null")); + } catch (Exception err) { + return new FailedPromise(err); + } + } + + public static void NoWait(IPromise promise) { + } + + [DebuggerStepThrough] + public static IPromise<T> Run<T>(Func<IPromise<T>> action) { ArgumentNotNull(action, "action"); try { @@ -124,5 +142,11 @@ return Promise<T>.FromException(err); } } + +#if NET_4_5 + public static void NoWait(Task t) { + } +#endif + } }
--- a/Implab/SuccessPromise.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/SuccessPromise.cs Tue Aug 22 12:45:01 2017 +0300 @@ -8,14 +8,8 @@ if (success != null) { try { success(); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; @@ -25,14 +19,8 @@ if (success != null) { try { success(); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this;
--- a/Implab/SuccessPromiseT.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/SuccessPromiseT.cs Tue Aug 22 12:45:01 2017 +0300 @@ -12,14 +12,8 @@ if (success != null) { try { success(m_value); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; @@ -29,14 +23,8 @@ if (success != null) { try { success(m_value); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; @@ -65,14 +53,8 @@ if (success != null) { try { success(); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; @@ -82,14 +64,8 @@ if (success != null) { try { success(); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this;
--- a/Implab/SyncContextPromise.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/Implab/SyncContextPromise.cs Tue Aug 22 12:45:01 2017 +0300 @@ -1,18 +1,21 @@ -using System.Threading; -using System; - -namespace Implab { - public class SyncContextPromise<T> : Promise<T> { - readonly SynchronizationContext m_context; - - public SyncContextPromise(SynchronizationContext context) { - Safe.ArgumentNotNull(context, "context"); - m_context = context; - } - - protected override void SignalHandler(HandlerDescriptor handler, int signal) { - m_context.Post(x => base.SignalHandler(handler, signal), null); - } - } -} - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Implab { + public class SyncContextPromise : Promise { + readonly SynchronizationContext m_context; + + public SyncContextPromise(SynchronizationContext context) { + Safe.ArgumentNotNull(context, "context"); + + m_context = context; + } + + protected override void SignalHandler(HandlerDescriptor handler, int signal) { + m_context.Post(x => base.SignalHandler(handler, signal), null); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/SyncContextPromiseT.cs Tue Aug 22 12:45:01 2017 +0300 @@ -0,0 +1,18 @@ +using System.Threading; +using System; + +namespace Implab { + public class SyncContextPromise<T> : Promise<T> { + readonly SynchronizationContext m_context; + + public SyncContextPromise(SynchronizationContext context) { + Safe.ArgumentNotNull(context, "context"); + m_context = context; + } + + protected override void SignalHandler(HandlerDescriptor handler, int signal) { + m_context.Post(x => base.SignalHandler(handler, signal), null); + } + } +} +
--- a/MonoPlay/Program.cs Tue Aug 22 09:35:54 2017 +0300 +++ b/MonoPlay/Program.cs Tue Aug 22 12:45:01 2017 +0300 @@ -1,42 +1,42 @@ -using System; -using Implab; -using System.Threading.Tasks; -using Implab.Formats.JSON; -using System.IO; -using System.Text.Json; +using Implab; +using Implab.Parallels; +using Implab.Diagnostics; +using System.Threading; namespace MonoPlay { class MainClass { public static void Main(string[] args) { - if (args == null) - throw new ArgumentNullException("args"); - int t1, t2; + var pool = new WorkerPool(10); + + var listerner = new ConsoleTraceListener(); + listerner.Subscribe<TraceEvent>(); + + TraceLog.StartLogicalOperation("Main"); - for (int i = 0; i < 2; i++) { - t1 = Environment.TickCount; - int elements =0; - using (var reader = new JSONParser(File.OpenText("/home/sergey/temp/citylots.json"))) { - while (reader.Read()) - elements++; - } + + var d = pool.Invoke(() => { + TraceLog.StartLogicalOperation("Worker"); + Thread.Sleep(100); + TraceLog.TraceInformation("worker done"); + TraceLog.EndLogicalOperation(); + }); - t2 = Environment.TickCount; - Console.WriteLine("attempt {0} done: {1} ms, {2:.00} Mb, {3} GC, Elements: {4}",i+1, t2 - t1, GC.GetTotalMemory(false) / (1024*1024), GC.CollectionCount(0), elements ); - } + var op = TraceContext.Instance.CurrentOperation; + ThreadPool.QueueUserWorkItem((o) => { + TraceContext.Instance.EnterLogicalOperation(op, false); + TraceLog.StartLogicalOperation("Thread"); + Thread.Sleep(100); + TraceLog.TraceInformation("thread done"); + TraceLog.EndLogicalOperation(); + }); - Console.WriteLine("Syste.Text.Json"); - var paraser = new JsonParser(); - for (int i = 0; i < 2; i++) { - t1 = Environment.TickCount; - using (var reader = File.OpenText("/home/sergey/temp/citylots.json")) { - paraser.Parse(reader); - } + TraceLog.TraceInformation("main done"); + TraceLog.EndLogicalOperation(); - t2 = Environment.TickCount; - Console.WriteLine("attempt {0} done: {1} ms, {2:.00} Mb, {3} GC, ",i+1, t2 - t1, GC.GetTotalMemory(false) / (1024*1024), GC.CollectionCount(0)); - } + d.Join(); + }