##// END OF EJS Templates
Added 'Fail' method to RunnableComponent which allows component to move from...
Added 'Fail' method to RunnableComponent which allows component to move from Running to Failed state. Added PollingComponent a timer based runnable component More tests Added FailPromise a thin class to wrap exceptions Fixed error handling in SuccessPromise classes.

File last commit:

r203:4d9830a9bbb8 v2
r203:4d9830a9bbb8 v2
Show More
PollingComponent.cs
155 lines | 5.5 KiB | text/x-csharp | CSharpLexer
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, Exception lastError) {
if (disposing)
Safe.Dispose(m_timer);
base.Dispose(disposing, lastError);
}
}
}