Skip to content

Commit

Permalink
Merge pull request #126 from vibe-d/issue_125_blocking_timer_callbacks
Browse files Browse the repository at this point in the history
Improve timer documentation and add a yieldLock the callback invocation
merged-on-behalf-of: Leonid Kramer <l-kramer@users.noreply.github.com>
  • Loading branch information
dlang-bot committed Jan 22, 2019
2 parents bee1c62 + 1284858 commit 6e04179
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 20 deletions.
91 changes: 72 additions & 19 deletions source/vibe/core/core.d
Original file line number Diff line number Diff line change
Expand Up @@ -730,17 +730,18 @@ unittest {
/**
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:
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
Returns:
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)
@safe nothrow {
Expand Down Expand Up @@ -777,14 +778,53 @@ Timer setTimer(Duration timeout, void delegate() callback, bool periodic = false
}, 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 {
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);
}


Expand Down Expand Up @@ -1031,16 +1071,22 @@ struct Timer {

@safe:

private this(TimerID id, Callback callback)
private static Timer create(CALLABLE)(TimerID id, CALLABLE callback)
nothrow {
assert(id != TimerID.init, "Invalid timer ID.");
m_driver = eventDriver;
m_id = id;

if (callback) {
m_driver.timers.userData!Callback(m_id) = callback;
m_driver.timers.wait(m_id, &TimerCallbackHandler.instance.handle);
}
Timer ret;
ret.m_driver = eventDriver;
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)
Expand Down Expand Up @@ -1078,6 +1124,8 @@ struct Timer {

/** Waits until the timer fires.
This method may only be used if no timer callback has been specified.
Returns:
`true` is returned $(I iff) the timer was fired.
*/
Expand All @@ -1094,12 +1142,15 @@ struct Timer {
}
}

struct TimerCallbackHandler {
static TimerCallbackHandler instance;
private struct TimerCallbackHandler(CALLABLE) {
static __gshared TimerCallbackHandler ms_instance;
static @property ref TimerCallbackHandler instance() @trusted nothrow { return ms_instance; }

void handle(TimerID timer, bool fired)
@safe nothrow {
if (fired) {
auto cb = eventDriver.timers.userData!(Timer.Callback)(timer);
auto cb = eventDriver.timers.userData!CALLABLE(timer);
auto l = yieldLock();
cb();
}

Expand All @@ -1118,8 +1169,10 @@ struct TimerCallbackHandler {
Multiple yield locks can appear in nested scopes.
*/
auto yieldLock()
{
@safe nothrow {
static struct YieldLock {
@safe nothrow:

private this(bool) { inc(); }
@disable this();
@disable this(this);
Expand Down
4 changes: 3 additions & 1 deletion source/vibe/core/task.d
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
debug (VibeTaskLog) logTrace("Resuming task with interrupt flag.");
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()
Expand Down

0 comments on commit 6e04179

Please sign in to comment.