Merge pull request #126 from vibe-d/issue_125_blocking_timer_callbacks

Improve timer documentation and add a yieldLock the callback invocation
merged-on-behalf-of: Leonid Kramer <l-kramer@users.noreply.github.com>
This commit is contained in:
The Dlang Bot 2019-01-22 21:03:47 +01:00 committed by GitHub
commit 6e04179cdc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 20 deletions

View file

@ -730,17 +730,18 @@ unittest {
/** /**
Returns a new armed timer. Returns a new armed timer.
Note that timers can only work if an event loop is running. Note that timers can only work if an event loop is running, explicitly or
implicitly by running a blocking operation, such as `sleep` or `File.read`.
Params: Params:
timeout = Determines the minimum amount of time that elapses before the timer fires. timeout = Determines the minimum amount of time that elapses before the timer fires.
callback = This delegate will be called when the timer fires callback = If non-`null`, this delegate will be called when the timer fires
periodic = Speficies if the timer fires repeatedly or only once periodic = Speficies if the timer fires repeatedly or only once
Returns: Returns:
Returns a Timer object that can be used to identify and modify the timer. Returns a Timer object that can be used to identify and modify the timer.
See_also: createTimer See_also: `createTimer`
*/ */
Timer setTimer(Duration timeout, Timer.Callback callback, bool periodic = false) Timer setTimer(Duration timeout, Timer.Callback callback, bool periodic = false)
@safe nothrow { @safe nothrow {
@ -777,14 +778,53 @@ Timer setTimer(Duration timeout, void delegate() callback, bool periodic = false
}, periodic); }, periodic);
} }
/**
Creates a new timer without arming it.
See_also: setTimer /** Creates a new timer without arming it.
Each time `callback` gets invoked, it will be run inside of a newly started
task.
Params:
callback = If non-`null`, this delegate will be called when the timer
fires
See_also: `createLeanTimer`, `setTimer`
*/ */
Timer createTimer(void delegate() nothrow @safe callback) Timer createTimer(void delegate() nothrow @safe callback = null)
@safe nothrow { @safe nothrow {
return Timer(eventDriver.timers.create, callback); static struct C {
void delegate() nothrow @safe callback;
void opCall() nothrow @safe { runTask(callback); }
}
if (callback) {
C c = {callback};
return createLeanTimer(c);
}
return createLeanTimer!(Timer.Callback)(null);
}
/** Creates a new timer with a lean callback mechanism.
In contrast to the standard `createTimer`, `callback` will not be called
in a new task, but is instead called directly in the context of the event
loop.
For this reason, the supplied callback is not allowed to perform any
operation that needs to block/yield execution. In this case, `runTask`
needs to be used explicitly to perform the operation asynchronously.
Additionally, `callback` can carry arbitrary state without requiring a heap
allocation.
See_also: `createTimer`
*/
Timer createLeanTimer(CALLABLE)(CALLABLE callback)
if (is(typeof(() @safe nothrow { callback(); } ())))
{
return Timer.create(eventDriver.timers.create(), callback);
} }
@ -1031,16 +1071,22 @@ struct Timer {
@safe: @safe:
private this(TimerID id, Callback callback) private static Timer create(CALLABLE)(TimerID id, CALLABLE callback)
nothrow { nothrow {
assert(id != TimerID.init, "Invalid timer ID."); assert(id != TimerID.init, "Invalid timer ID.");
m_driver = eventDriver;
m_id = id;
if (callback) { Timer ret;
m_driver.timers.userData!Callback(m_id) = callback; ret.m_driver = eventDriver;
m_driver.timers.wait(m_id, &TimerCallbackHandler.instance.handle); ret.m_id = id;
}
static if (is(typeof(!callback)))
if (!callback)
return ret;
ret.m_driver.timers.userData!CALLABLE(id) = callback;
ret.m_driver.timers.wait(id, &TimerCallbackHandler!CALLABLE.instance.handle);
return ret;
} }
this(this) this(this)
@ -1078,6 +1124,8 @@ struct Timer {
/** Waits until the timer fires. /** Waits until the timer fires.
This method may only be used if no timer callback has been specified.
Returns: Returns:
`true` is returned $(I iff) the timer was fired. `true` is returned $(I iff) the timer was fired.
*/ */
@ -1094,12 +1142,15 @@ struct Timer {
} }
} }
struct TimerCallbackHandler { private struct TimerCallbackHandler(CALLABLE) {
static TimerCallbackHandler instance; static __gshared TimerCallbackHandler ms_instance;
static @property ref TimerCallbackHandler instance() @trusted nothrow { return ms_instance; }
void handle(TimerID timer, bool fired) void handle(TimerID timer, bool fired)
@safe nothrow { @safe nothrow {
if (fired) { if (fired) {
auto cb = eventDriver.timers.userData!(Timer.Callback)(timer); auto cb = eventDriver.timers.userData!CALLABLE(timer);
auto l = yieldLock();
cb(); cb();
} }
@ -1118,8 +1169,10 @@ struct TimerCallbackHandler {
Multiple yield locks can appear in nested scopes. Multiple yield locks can appear in nested scopes.
*/ */
auto yieldLock() auto yieldLock()
{ @safe nothrow {
static struct YieldLock { static struct YieldLock {
@safe nothrow:
private this(bool) { inc(); } private this(bool) { inc(); }
@disable this(); @disable this();
@disable this(this); @disable this(this);

View file

@ -491,7 +491,9 @@ final package class TaskFiber : Fiber {
} else assert(() @trusted { return Thread.getThis(); } () is this.thread, "Interrupting tasks in different threads is not yet supported."); } else assert(() @trusted { return Thread.getThis(); } () is this.thread, "Interrupting tasks in different threads is not yet supported.");
debug (VibeTaskLog) logTrace("Resuming task with interrupt flag."); debug (VibeTaskLog) logTrace("Resuming task with interrupt flag.");
m_interrupt = true; m_interrupt = true;
taskScheduler.switchTo(this.task);
auto defer = TaskFiber.getThis().m_yieldLockCount > 0 ? Yes.defer : No.defer;
taskScheduler.switchTo(this.task, defer);
} }
void bumpTaskCounter() void bumpTaskCounter()