Mercurial > pub > ImplabNet
changeset 205:8200ab154c8a v2
Added ResetState to RunnableComponent to reset in case of failure
Added StateChanged event to IRunnable
Renamed Promise.SUCCESS -> Promise.Success
Added Promise.FromException
Renamed Bundle -> PromiseAll in PromiseExtensions
line wrap: on
line diff
--- a/Implab.Test/AsyncTests.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab.Test/AsyncTests.cs Tue Oct 25 17:40:33 2016 +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 Oct 18 17:49:54 2016 +0300 +++ b/Implab.Test/Implab.Format.Test/Implab.Format.Test.csproj Tue Oct 25 17:40:33 2016 +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.mono.csproj Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab.Test/Implab.Test.mono.csproj Tue Oct 25 17:40:33 2016 +0300 @@ -1,75 +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" /> - <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> - <Folder Include="Mock\" /> - </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
--- a/Implab.Test/Mock/MockPollingComponent.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab.Test/Mock/MockPollingComponent.cs Tue Oct 25 17:40:33 2016 +0300 @@ -50,7 +50,7 @@ } protected override IPromise OnTick(ICancellationToken cancellationToken) { - return MockTick != null ? Safe.Run(() => MockTick(cancellationToken)) : Promise.SUCCESS; + return MockTick != null ? Safe.Run(() => MockTick(cancellationToken)) : Promise.Success; } protected override void OnTickCancel(Exception error) {
--- a/Implab.Test/Mock/MockRunnableComponent.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab.Test/Mock/MockRunnableComponent.cs Tue Oct 25 17:40:33 2016 +0300 @@ -6,6 +6,9 @@ public MockRunnableComponent(bool initialized) : base(initialized) { } + public MockRunnableComponent(bool initialized, bool reusable) : base(initialized, reusable) { + } + public Action MockInit { get; set; @@ -21,6 +24,11 @@ set; } + public Action<bool, Exception> MockDispose { + get; + set; + } + protected override IPromise OnStart() { return MockStart != null ? Safe.Run(MockStart).Chain(base.OnStart) : Safe.Run(base.OnStart); } @@ -33,6 +41,12 @@ if (MockInit != null) MockInit(); } + + protected override void Dispose(bool disposing, Exception lastError) { + if (MockDispose != null) + MockDispose(disposing, lastError); + base.Dispose(disposing, lastError); + } } }
--- a/Implab.Test/PollingComponentTests.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab.Test/PollingComponentTests.cs Tue Oct 25 17:40:33 2016 +0300 @@ -36,7 +36,7 @@ Assert.AreEqual(ExecutionState.Created, comp.State); - comp.Init(); + comp.Initialize(); Assert.AreEqual(ExecutionState.Ready, comp.State); @@ -57,7 +57,7 @@ var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); comp.MockTick = ct => { signal.Set(); - return Promise.SUCCESS; + return Promise.Success; }; comp.Start().Join(1000);
--- a/Implab.Test/RunnableComponentTests.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab.Test/RunnableComponentTests.cs Tue Oct 25 17:40:33 2016 +0300 @@ -39,7 +39,7 @@ Assert.AreEqual(ExecutionState.Created, comp.State); - comp.Init(); + comp.Initialize(); Assert.AreEqual(ExecutionState.Ready, comp.State); @@ -65,7 +65,7 @@ ShouldThrow(() => comp.Stop()); Assert.AreEqual(ExecutionState.Created, comp.State); - ShouldThrow(comp.Init); + ShouldThrow(comp.Initialize); Assert.AreEqual(ExecutionState.Failed, comp.State); @@ -85,12 +85,77 @@ 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, error) => { + 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, error) => { + 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; + Exception lastError = null; + comp.MockDispose = (disposing, error) => { + disposed = true; + lastError = error; + }; + + 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 MockRunnableComponent(true) { MockStart = () => PromiseHelper.Sleep(100000, 0)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/packages.config Tue Oct 25 17:40:33 2016 +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 Oct 18 17:49:54 2016 +0300 +++ b/Implab/Automaton/AutomatonTransition.cs Tue Oct 25 17:40:33 2016 +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/IInitializable.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab/Components/IInitializable.cs Tue Oct 25 17:40:33 2016 +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 Oct 18 17:49:54 2016 +0300 +++ b/Implab/Components/IRunnable.cs Tue Oct 25 17:40:33 2016 +0300 @@ -14,6 +14,8 @@ ExecutionState State { get; } + event EventHandler<StateChangeEventArgs> StateChanged; + Exception LastError { get; } } }
--- a/Implab/Components/PollingComponent.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab/Components/PollingComponent.cs Tue Oct 25 17:40:33 2016 +0300 @@ -124,7 +124,7 @@ /// Invoked when the timer ticks, use this method to implement your logic /// </summary> protected virtual IPromise OnTick(ICancellationToken cancellationToken) { - return Promise.SUCCESS; + return Promise.Success; } protected override IPromise OnStop() {
--- a/Implab/Components/RunnableComponent.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab/Components/RunnableComponent.cs Tue Oct 25 17:40:33 2016 +0300 @@ -9,7 +9,8 @@ Start, Stop, Dispose, - Last = Dispose + Reset, + Last = Reset } class StateMachine { @@ -37,9 +38,11 @@ Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose); Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail); - Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok); + Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok); + Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose); Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose); + Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset); } static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) { @@ -67,11 +70,26 @@ IPromise m_pending; Exception m_lastError; - readonly StateMachine m_stateMachine; - - protected RunnableComponent(bool initialized) { + 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(initialized ? ExecutionState.Ready : ExecutionState.Created); - DisposeTimeout = 10000; + m_reusable = reusable; + DisposeTimeout = 10000; + } + + /// <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) { } /// <summary> @@ -84,49 +102,119 @@ 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; + } + + protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) { + var h = StateChanged; + if (h != null) + h(this, new StateChangeEventArgs { + State = current, + LastError = error + }); } /// <summary> /// Moves the component from running to failed state. /// </summary> /// <param name="error">The exception which is describing the error.</param> - /// <returns>Returns true if the component is set to the failed state, false - otherwise. - /// This method works only for the running state, in any other state it will return false.</returns> - protected bool Fail(Exception error) { - lock (m_stateMachine) { - if(m_stateMachine.State == ExecutionState.Running) { - m_stateMachine.Move(Commands.Fail); - m_lastError = error; - return true; - } - } - return false; + protected bool Fail(Exception error) { + return MoveIfInState(Commands.Fail, null, error, ExecutionState.Running); } - void Invoke(Commands cmd, Action action) { - lock (m_stateMachine) - Move(cmd); - + /// <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) { @@ -135,40 +223,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(); @@ -181,8 +249,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() { @@ -197,15 +273,16 @@ } protected virtual IPromise OnStart() { - return Promise.SUCCESS; + return Promise.Success; } public IPromise Stop() { - return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose); + var pending = InvokeAsync(Commands.Stop, OnStop, StopPending); + return m_reusable ? pending : pending.Then(Dispose); } protected virtual IPromise OnStop() { - return Promise.SUCCESS; + return Promise.Success; } /// <summary> @@ -258,17 +335,14 @@ /// </para></remarks> public void Dispose() { IPromise pending; + lock (m_stateMachine) { if (m_stateMachine.State == ExecutionState.Disposed) return; - - Move(Commands.Dispose); + pending = Move(Commands.Dispose, null, null); + } - GC.SuppressFinalize(this); - - pending = m_pending; - m_pending = null; - } + GC.SuppressFinalize(this); if (pending != null) { pending.Cancel(); pending.Timeout(DisposeTimeout).On( @@ -277,7 +351,7 @@ reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason)) ); } else { - Dispose(true, m_lastError); + Dispose(true, null); } } @@ -287,6 +361,11 @@ #endregion + /// <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="lastError">The last error which occured during the component stop.</param> protected virtual void Dispose(bool disposing, Exception lastError) { }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Components/StateChangeEventArgs.cs Tue Oct 25 17:40:33 2016 +0300 @@ -0,0 +1,16 @@ +using System; + +namespace Implab.Components +{ + public class StateChangeEventArgs { + /// <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/TextFileListener.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab/Diagnostics/TextFileListener.cs Tue Oct 25 17:40:33 2016 +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); @@ -20,7 +21,7 @@ msg.Append(" "); msg.AppendFormat("[{0}]:{1}: {2}", args.ThreadId, args.Channel, entry); - 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); } }
--- a/Implab/Implab.csproj Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab/Implab.csproj Tue Oct 25 17:40:33 2016 +0300 @@ -75,6 +75,7 @@ <Reference Include="mscorlib" /> </ItemGroup> <ItemGroup> + <Compile Include="Components\StateChangeEventArgs.cs" /> <Compile Include="CustomEqualityComparer.cs" /> <Compile Include="Diagnostics\ConsoleTraceListener.cs" /> <Compile Include="Diagnostics\LogChannel.cs" /> @@ -269,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 Oct 18 17:49:54 2016 +0300 +++ b/Implab/Promise.cs Tue Oct 25 17:40:33 2016 +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 Oct 18 17:49:54 2016 +0300 +++ b/Implab/PromiseExtensions.cs Tue Oct 25 17:40:33 2016 +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) { @@ -75,7 +76,7 @@ ((ICancellable)cookie).Cancel(new TimeoutException()); } - /// <summary> +/// <summary> /// Cancells promise after the specified timeout is elapsed. /// </summary> /// <param name="that">The promise to cancel on timeout.</param> @@ -88,7 +89,17 @@ 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) { + Safe.ArgumentNotNull(that, "that"); + return PromiseAll(that.ToList()); + } + + public static IPromise PromiseAll(this ICollection<IPromise> that) { Safe.ArgumentNotNull(that, "that"); int count = that.Count; @@ -128,7 +139,7 @@ return medium; } - public static IPromise<T[]> Bundle<T>(this ICollection<IPromise<T>> that) { + public static IPromise<T[]> PromiseAll<T>(this ICollection<IPromise<T>> that) { Safe.ArgumentNotNull(that, "that"); int count = that.Count;