Commit b073f6df authored by Kirill Smelkov's avatar Kirill Smelkov

time: Move/Port timers to C++/Pyx nogil

Provide time.Ticker, time.Timer and friends that can be used directly
from C++ and Pyx/nogil codes. Python-level classes become small wrapper
around pyx/nogil ones.

This is the first patch that moves to Pyx/nogil classes that are
dynamically allocated on heap. refptr<T> is used to automatically manage
lifetime of such objects. At Pyx level exposed API is very similar to
Python-one, while internally it uses refptr<T> and friends.
parent e614d641
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
See also https://golang.org/pkg/time for Go time package documentation. See also https://golang.org/pkg/time for Go time package documentation.
""" """
from golang cimport chan, cbool, refptr
from libcpp cimport nullptr_t
# golang/pyx - the same as std python - represents time as float # golang/pyx - the same as std python - represents time as float
cdef extern from * nogil: cdef extern from * nogil:
# XXX how to declare/share constants without C verbatim? # XXX how to declare/share constants without C verbatim?
...@@ -53,3 +56,36 @@ cdef extern from * nogil: ...@@ -53,3 +56,36 @@ cdef extern from * nogil:
cdef extern from "golang/time.h" namespace "golang::time" nogil: cdef extern from "golang/time.h" namespace "golang::time" nogil:
void sleep(double dt) void sleep(double dt)
double now() double now()
chan[double] tick(double dt)
chan[double] after(double dt)
Timer after_func(double dt, ...) # ... = std::function<void()>
# pyx _Ticker = raw C++ Ticker
cppclass _Ticker "Ticker":
chan[double] c
void stop()
# pyx Ticker = C++ refptr<Ticker>
cppclass Ticker "golang::refptr<golang::time::Ticker>" (refptr[_Ticker]):
# Ticker.X = Ticker->X in C++.
chan[double] c "_ptr()->c"
void stop "_ptr()->stop" ()
Ticker new_ticker(double dt)
# pyx _Timer = raw C++ Timer
cppclass _Timer "Timer":
chan[double] c
cbool stop()
void reset(double dt)
# pyx Timer = C++ refptr<Timer>
cppclass Timer "golang::refptr<golang::time::Timer>" (refptr[_Timer]):
# Timer.X = Timer->X in C++.
chan[double] c "_ptr()->c"
cbool stop "_ptr()->stop" ()
void reset "_ptr()->reset" (double dt)
Timer new_timer(double dt)
This diff is collapsed.
...@@ -22,9 +22,181 @@ ...@@ -22,9 +22,181 @@
#include "golang/time.h" #include "golang/time.h"
#include <math.h>
using std::function;
// golang::time:: (except sleep and now) // golang::time:: (except sleep and now)
namespace golang { namespace golang {
namespace time { namespace time {
// ---- timers ----
// FIXME timers are implemented very inefficiently - each timer currently consumes a goroutine.
refptr<Ticker> new_ticker(double dt);
refptr<Timer> new_timer (double dt);
refptr<Timer> _new_timer(double dt, function<void()>);
chan<double> tick(double dt) {
if (dt <= 0)
return NULL;
return new_ticker(dt)->c;
}
chan<double> after(double dt) {
return new_timer(dt)->c;
}
refptr<Timer> after_func(double dt, function<void()> f) {
return _new_timer(dt, f);
}
// Ticker
Ticker::Ticker() {}
Ticker::~Ticker() {}
void Ticker::decref() {
if (__decref())
delete this;
}
refptr<Ticker> new_ticker(double dt) {
if (dt <= 0)
panic("ticker: dt <= 0");
refptr<Ticker> tx = adoptref(new Ticker());
tx->c = makechan<double>(1); // 1-buffer -- same as in Go
tx->_dt = dt;
tx->_stop = false;
go([tx]() {
tx->_tick();
});
return tx;
}
void Ticker::stop() {
Ticker &tx = *this;
tx._mu.lock();
tx._stop = true;
// drain what _tick could have been queued already
while (tx.c.len() > 0)
tx.c.recv();
tx._mu.unlock();
}
void Ticker::_tick() {
Ticker &tx = *this;
while (1) {
// XXX adjust for accumulated error δ?
sleep(tx._dt);
tx._mu.lock();
if (tx._stop) {
tx._mu.unlock();
return;
}
// send from under ._mu so that .stop can be sure there is no
// ongoing send while it drains the channel.
double t = now();
select({
_default,
tx.c.sends(&t),
});
tx._mu.unlock();
}
}
// Timer
Timer::Timer() {}
Timer::~Timer() {}
void Timer::decref() {
if (__decref())
delete this;
}
refptr<Timer> _new_timer(double dt, function<void()> f) {
refptr<Timer> t = adoptref(new Timer());
t->c = (f == NULL ? makechan<double>(1) : NULL);
t->_f = f;
t->_dt = INFINITY;
t->_ver = 0;
t->reset(dt);
return t;
}
refptr<Timer> new_timer(double dt) {
return _new_timer(dt, NULL);
}
bool Timer::stop() {
Timer &t = *this;
bool canceled;
t._mu.lock();
if (t._dt == INFINITY) {
canceled = false;
}
else {
t._dt = INFINITY;
t._ver += 1;
canceled = true;
}
// drain what _fire could have been queued already
while (t.c.len() > 0)
t.c.recv();
t._mu.unlock();
return canceled;
}
void Timer::reset(double dt) {
Timer &t = *this;
t._mu.lock();
if (t._dt != INFINITY) {
t._mu.unlock();
panic("Timer.reset: the timer is armed; must be stopped or expired");
}
t._dt = dt;
t._ver += 1;
// TODO rework timers so that new timer does not spawn new goroutine.
refptr<Timer> tref = newref(&t); // pass t reference to spawned goroutine
go([tref, dt](int ver) {
tref->_fire(dt, ver);
}, t._ver);
t._mu.unlock();
}
void Timer::_fire(double dt, int ver) {
Timer &t = *this;
sleep(dt);
t._mu.lock();
if (t._ver != ver) {
t._mu.unlock();
return; // the timer was stopped/resetted - don't fire it
}
t._dt = INFINITY;
// send under ._mu so that .stop can be sure that if it sees
// ._dt = INFINITY, there is no ongoing .c send.
if (t._f == NULL) {
t.c.send(now());
t._mu.unlock();
return;
}
t._mu.unlock();
// call ._f not from under ._mu not to deadlock e.g. if ._f wants to reset the timer.
t._f();
}
}} // golang::time:: }} // golang::time::
...@@ -24,6 +24,9 @@ ...@@ -24,6 +24,9 @@
// //
// - `now` returns current time. // - `now` returns current time.
// - `sleep` pauses current task. // - `sleep` pauses current task.
// - `Ticker` and `Timer` provide timers integrated with channels.
// - `tick`, `after` and `after_func` are convenience wrappers to use
// tickers and timers easily.
// //
// See also https://golang.org/pkg/time for Go time package documentation. // See also https://golang.org/pkg/time for Go time package documentation.
// //
...@@ -37,6 +40,7 @@ ...@@ -37,6 +40,7 @@
#include <golang/libgolang.h> #include <golang/libgolang.h>
#include <golang/sync.h>
// ---- C-level API ---- // ---- C-level API ----
...@@ -69,6 +73,112 @@ LIBGOLANG_API void sleep(double dt); ...@@ -69,6 +73,112 @@ LIBGOLANG_API void sleep(double dt);
// now returns current time in seconds. // now returns current time in seconds.
LIBGOLANG_API double now(); LIBGOLANG_API double now();
class Ticker;
class Timer;
// tick returns channel connected to dt ticker.
//
// Note: there is no way to stop created ticker.
// Note: for dt <= 0, contrary to Ticker, tick returns nil channel instead of panicking.
LIBGOLANG_API chan<double> tick(double dt);
// after returns channel connected to dt timer.
//
// Note: with after there is no way to stop/garbage-collect created timer until it fires.
LIBGOLANG_API chan<double> after(double dt);
// after_func arranges to call f after dt time.
//
// The function will be called in its own goroutine.
// Returned timer can be used to cancel the call.
LIBGOLANG_API refptr<Timer> after_func(double dt, std::function<void()> f);
// new_ticker creates new Ticker that will be firing at dt intervals.
LIBGOLANG_API refptr<Ticker> new_ticker(double dt);
// Ticker arranges for time events to be sent to .c channel on dt-interval basis.
//
// If the receiver is slow, Ticker does not queue events and skips them.
// Ticking can be canceled via .stop() .
struct Ticker : refobj {
chan<double> c;
private:
double _dt;
sync::Mutex _mu;
bool _stop;
// don't new - create only via new_ticker()
private:
Ticker();
~Ticker();
friend refptr<Ticker> new_ticker(double dt);
public:
LIBGOLANG_API void decref();
public:
// stop cancels the ticker.
//
// It is guaranteed that ticker channel is empty after stop completes.
LIBGOLANG_API void stop();
private:
void _tick();
};
// new_timer creates new Timer that will fire after dt.
LIBGOLANG_API refptr<Timer> new_timer(double dt);
// Timer arranges for time event to be sent to .c channel after dt time.
//
// The timer can be stopped (.stop), or reinitialized to another time (.reset).
struct Timer : refobj {
chan<double> c;
private:
std::function<void()> _f;
sync::Mutex _mu;
double _dt; // +inf - stopped, otherwise - armed
int _ver; // current timer was armed by n'th reset
// don't new - create only via new_timer() & co
private:
Timer();
~Timer();
friend refptr<Timer> _new_timer(double dt, std::function<void()> f);
public:
LIBGOLANG_API void decref();
public:
// stop cancels the timer.
//
// It returns:
//
// False: the timer was already expired or stopped,
// True: the timer was armed and canceled by this stop call.
//
// Note: contrary to Go version, there is no need to drain timer channel
// after stop call - it is guaranteed that after stop the channel is empty.
//
// Note: similarly to Go, if Timer is used with function - it is not
// guaranteed that after stop the function is not running - in such case
// the caller must explicitly synchronize with that function to complete.
LIBGOLANG_API bool stop();
// reset rearms the timer.
//
// the timer must be either already stopped or expired.
LIBGOLANG_API void reset(double dt);
private:
void _fire(double dt, int ver);
};
}} // golang::time:: }} // golang::time::
#endif // __cplusplus #endif // __cplusplus
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment