Initial version with sone partial Posix implementations.
This commit is contained in:
commit
2a926d87aa
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.dub
|
3
dub.sdl
Normal file
3
dub.sdl
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name "eventcore"
|
||||||
|
description "Callback based abstraction layer over operating system asynchronous I/O facilities."
|
||||||
|
|
4
examples/http-server-fibers/dub.sdl
Normal file
4
examples/http-server-fibers/dub.sdl
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
name "http-server-example"
|
||||||
|
description "Simple pseudo HTTP server suitable for benchmarking"
|
||||||
|
dependency "eventcore" path="../.."
|
||||||
|
|
229
examples/http-server-fibers/source/app.d
Normal file
229
examples/http-server-fibers/source/app.d
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
|
||||||
|
import eventcore.core;
|
||||||
|
import eventcore.internal.utils;
|
||||||
|
import std.functional : toDelegate;
|
||||||
|
import std.socket : InternetAddress;
|
||||||
|
import std.exception : enforce;
|
||||||
|
import std.typecons : Rebindable, RefCounted;
|
||||||
|
import core.thread : Fiber;
|
||||||
|
|
||||||
|
|
||||||
|
Fiber[] store = new Fiber[20000];
|
||||||
|
size_t storeSize = 0;
|
||||||
|
Fiber getFiber()
|
||||||
|
nothrow {
|
||||||
|
|
||||||
|
if (storeSize > 0) return store[--storeSize];
|
||||||
|
return new Fiber({});
|
||||||
|
}
|
||||||
|
void done(Fiber f)
|
||||||
|
nothrow {
|
||||||
|
if (storeSize < store.length)
|
||||||
|
store[storeSize++] = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct AsyncBlocker {
|
||||||
|
@safe:
|
||||||
|
|
||||||
|
bool done;
|
||||||
|
Rebindable!(const(Exception)) exception;
|
||||||
|
Fiber owner;
|
||||||
|
|
||||||
|
void start()
|
||||||
|
nothrow {
|
||||||
|
assert(owner is null);
|
||||||
|
done = false;
|
||||||
|
exception = null;
|
||||||
|
() @trusted { owner = Fiber.getThis(); } ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait()
|
||||||
|
{
|
||||||
|
() @trusted { while (!done) Fiber.yield(); } ();
|
||||||
|
auto ex = cast(const(Exception))exception;
|
||||||
|
owner = null;
|
||||||
|
done = false;
|
||||||
|
exception = null;
|
||||||
|
if (ex) throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish(const(Exception) e = null)
|
||||||
|
nothrow {
|
||||||
|
assert(!done && owner !is null);
|
||||||
|
exception = e;
|
||||||
|
done = true;
|
||||||
|
() @trusted { scope (failure) assert(false); if (owner.state == Fiber.State.HOLD) owner.call(); } ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias StreamConnection = RefCounted!StreamConnectionImpl;
|
||||||
|
|
||||||
|
struct StreamConnectionImpl {
|
||||||
|
@safe: /*@nogc:*/
|
||||||
|
private {
|
||||||
|
StreamSocketFD m_socket;
|
||||||
|
bool m_empty = false;
|
||||||
|
|
||||||
|
AsyncBlocker writer;
|
||||||
|
AsyncBlocker reader;
|
||||||
|
ubyte[] m_readBuffer;
|
||||||
|
size_t m_readBufferFill;
|
||||||
|
|
||||||
|
ubyte[] m_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(StreamSocketFD sock, ubyte[] buffer)
|
||||||
|
nothrow {
|
||||||
|
m_socket = sock;
|
||||||
|
m_readBuffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
~this()
|
||||||
|
nothrow {
|
||||||
|
if (m_socket != StreamSocketFD.invalid)
|
||||||
|
eventDriver.releaseRef(m_socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@property bool empty()
|
||||||
|
{
|
||||||
|
reader.start();
|
||||||
|
eventDriver.waitSocketData(m_socket, &onData);
|
||||||
|
reader.wait();
|
||||||
|
return m_empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte[] readLine()
|
||||||
|
{
|
||||||
|
reader.start();
|
||||||
|
if (m_readBufferFill >= 2) onReadLineData(m_socket, IOStatus.ok, 0);
|
||||||
|
else eventDriver.readSocket(m_socket, m_readBuffer[m_readBufferFill .. $], &onReadLineData, IOMode.once);
|
||||||
|
reader.wait();
|
||||||
|
auto ln = m_line;
|
||||||
|
m_line = null;
|
||||||
|
return ln;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const(ubyte)[] data)
|
||||||
|
{
|
||||||
|
writer.start();
|
||||||
|
eventDriver.writeSocket(m_socket, data, &onWrite, IOMode.all);
|
||||||
|
writer.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void close()
|
||||||
|
nothrow {
|
||||||
|
eventDriver.releaseRef(m_socket);
|
||||||
|
m_socket = StreamSocketFD.invalid;
|
||||||
|
m_readBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onWrite(StreamSocketFD fd, IOStatus status, size_t len)
|
||||||
|
@safe nothrow {
|
||||||
|
static const ex = new Exception("Failed to write data!");
|
||||||
|
writer.finish(status == IOStatus.ok ? null : ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onData(StreamSocketFD, IOStatus status, size_t bytes_read)
|
||||||
|
@safe nothrow {
|
||||||
|
if (status != IOStatus.ok)
|
||||||
|
m_empty = true;
|
||||||
|
reader.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onReadLineData(StreamSocketFD, IOStatus status, size_t bytes_read)
|
||||||
|
@safe nothrow {
|
||||||
|
static const ex = new Exception("Failed to read data!");
|
||||||
|
static const exh = new Exception("Header line too long.");
|
||||||
|
|
||||||
|
import std.algorithm : countUntil;
|
||||||
|
|
||||||
|
if (status != IOStatus.ok) {
|
||||||
|
reader.finish(ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_readBufferFill += bytes_read;
|
||||||
|
|
||||||
|
assert(m_readBufferFill <= m_readBuffer.length);
|
||||||
|
|
||||||
|
auto idx = m_readBuffer[0 .. m_readBufferFill].countUntil(cast(const(ubyte)[])"\r\n");
|
||||||
|
if (idx >= 0) {
|
||||||
|
m_readBuffer[m_readBufferFill .. m_readBufferFill + idx] = m_readBuffer[0 .. idx];
|
||||||
|
foreach (i; 0 .. m_readBufferFill - idx - 2)
|
||||||
|
m_readBuffer[i] = m_readBuffer[idx+2+i];
|
||||||
|
m_readBufferFill -= idx + 2;
|
||||||
|
|
||||||
|
m_line = m_readBuffer[m_readBufferFill + idx + 2 .. m_readBufferFill + idx + 2 + idx];
|
||||||
|
|
||||||
|
reader.finish();
|
||||||
|
} else if (m_readBuffer.length - m_readBufferFill > 0) {
|
||||||
|
eventDriver.readSocket(m_socket, m_readBuffer[m_readBufferFill .. $], &onReadLineData, IOMode.once);
|
||||||
|
} else {
|
||||||
|
reader.finish(exh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
print("Starting up...");
|
||||||
|
auto addr = new InternetAddress("127.0.0.1", 8080);
|
||||||
|
auto listener = eventDriver.listenStream(addr, toDelegate(&onClientConnect));
|
||||||
|
enforce(listener != StreamListenSocketFD.invalid, "Failed to listen for connections.");
|
||||||
|
|
||||||
|
import core.time : msecs;
|
||||||
|
eventDriver.setTimer(eventDriver.createTimer((tm) { print("timer 1"); }), 1000.msecs, 1000.msecs);
|
||||||
|
eventDriver.setTimer(eventDriver.createTimer((tm) { print("timer 2"); }), 250.msecs, 500.msecs);
|
||||||
|
|
||||||
|
print("Listening for requests on port 8080...");
|
||||||
|
while (eventDriver.waiterCount)
|
||||||
|
eventDriver.processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClientConnect(StreamListenSocketFD listener, StreamSocketFD client)
|
||||||
|
@trusted /*@nogc*/ nothrow {
|
||||||
|
import core.stdc.stdlib;
|
||||||
|
auto handler = cast(ClientHandler*)calloc(1, ClientHandler.sizeof);
|
||||||
|
handler.client = client;
|
||||||
|
auto f = getFiber();
|
||||||
|
f.reset(&handler.handleConnection);
|
||||||
|
scope (failure) assert(false);
|
||||||
|
f.call();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ClientHandler {
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
|
||||||
|
StreamSocketFD client;
|
||||||
|
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
void handleConnection()
|
||||||
|
@trusted {
|
||||||
|
ubyte[512] linebuf = void;
|
||||||
|
auto reply = cast(const(ubyte)[])"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\nKeep-Alive: timeout=10\r\n\r\nHello, World!";
|
||||||
|
|
||||||
|
auto conn = StreamConnection(client, linebuf);
|
||||||
|
try {
|
||||||
|
while (!conn.empty) {
|
||||||
|
conn.readLine();
|
||||||
|
|
||||||
|
ubyte[] ln;
|
||||||
|
do ln = conn.readLine();
|
||||||
|
while (ln.length > 0);
|
||||||
|
|
||||||
|
conn.write(reply);
|
||||||
|
}
|
||||||
|
//print("close %s", cast(int)client);
|
||||||
|
} catch (Exception e) {
|
||||||
|
print("close %s: %s", cast(int)client, e.msg);
|
||||||
|
}
|
||||||
|
conn.close();
|
||||||
|
|
||||||
|
done(Fiber.getThis());
|
||||||
|
}
|
||||||
|
}
|
4
examples/http-server/dub.sdl
Normal file
4
examples/http-server/dub.sdl
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
name "http-server-example"
|
||||||
|
description "Simple pseudo HTTP server suitable for benchmarking"
|
||||||
|
dependency "eventcore" path="../.."
|
||||||
|
|
112
examples/http-server/source/app.d
Normal file
112
examples/http-server/source/app.d
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
|
||||||
|
import eventcore.core;
|
||||||
|
import eventcore.internal.utils;
|
||||||
|
import std.functional : toDelegate;
|
||||||
|
import std.socket : InternetAddress;
|
||||||
|
import std.exception : enforce;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
print("Starting up...");
|
||||||
|
auto addr = new InternetAddress("127.0.0.1", 8080);
|
||||||
|
auto listener = eventDriver.listenStream(addr, toDelegate(&onClientConnect));
|
||||||
|
enforce(listener != StreamListenSocketFD.invalid, "Failed to listen for connections.");
|
||||||
|
|
||||||
|
print("Listening for requests on port 8080...");
|
||||||
|
while (eventDriver.waiterCount)
|
||||||
|
eventDriver.processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClientConnect(StreamListenSocketFD listener, StreamSocketFD client)
|
||||||
|
@trusted /*@nogc*/ nothrow {
|
||||||
|
import core.stdc.stdlib;
|
||||||
|
auto handler = cast(ClientHandler*)calloc(1, ClientHandler.sizeof);
|
||||||
|
handler.client = client;
|
||||||
|
handler.handleConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ClientHandler {
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
|
||||||
|
alias LineCallback = void delegate(ubyte[]);
|
||||||
|
|
||||||
|
StreamSocketFD client;
|
||||||
|
ubyte[512] linebuf = void;
|
||||||
|
size_t linefill = 0;
|
||||||
|
LineCallback onLine;
|
||||||
|
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
void handleConnection()
|
||||||
|
{
|
||||||
|
int fd = client;
|
||||||
|
//import core.thread;
|
||||||
|
//() @trusted { print("Connection %d %s", fd, cast(void*)Thread.getThis()); } ();
|
||||||
|
readLine(&onRequestLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
void readLine(LineCallback on_line)
|
||||||
|
{
|
||||||
|
onLine = on_line;
|
||||||
|
if (linefill >= 2) onReadData(client, IOStatus.ok, 0);
|
||||||
|
else eventDriver.readSocket(client, linebuf[linefill .. $], &onReadData, IOMode.once);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRequestLine(ubyte[] ln)
|
||||||
|
{
|
||||||
|
//print("Request: %s", cast(char[])ln);
|
||||||
|
if (ln.length == 0) {
|
||||||
|
//print("Error: empty request line");
|
||||||
|
eventDriver.shutdownSocket(client);
|
||||||
|
eventDriver.releaseRef(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
readLine(&onHeaderLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onHeaderLine(ubyte[] ln)
|
||||||
|
{
|
||||||
|
if (ln.length == 0) {
|
||||||
|
auto reply = cast(const(ubyte)[])"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\nKeep-Alive: timeout=10\r\n\r\nHello, World!";
|
||||||
|
eventDriver.writeSocket(client, reply, &onWriteFinished, IOMode.all);
|
||||||
|
} else readLine(&onHeaderLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onWriteFinished(StreamSocketFD fd, IOStatus status, size_t len)
|
||||||
|
{
|
||||||
|
readLine(&onRequestLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onReadData(StreamSocketFD, IOStatus status, size_t bytes_read)
|
||||||
|
{
|
||||||
|
import std.algorithm : countUntil;
|
||||||
|
|
||||||
|
if (status != IOStatus.ok) {
|
||||||
|
print("Client disconnect");
|
||||||
|
eventDriver.shutdownSocket(client);
|
||||||
|
eventDriver.releaseRef(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
linefill += bytes_read;
|
||||||
|
|
||||||
|
assert(linefill <= linebuf.length);
|
||||||
|
|
||||||
|
auto idx = linebuf[0 .. linefill].countUntil(cast(const(ubyte)[])"\r\n");
|
||||||
|
if (idx >= 0) {
|
||||||
|
linebuf[linefill .. linefill + idx] = linebuf[0 .. idx];
|
||||||
|
foreach (i; 0 .. linefill - idx - 2)
|
||||||
|
linebuf[i] = linebuf[idx+2+i];
|
||||||
|
linefill -= idx + 2;
|
||||||
|
|
||||||
|
onLine(linebuf[linefill + idx + 2 .. linefill + idx + 2 + idx]);
|
||||||
|
} else if (linebuf.length - linefill > 0) {
|
||||||
|
eventDriver.readSocket(client, linebuf[linefill .. $], &onReadData, IOMode.once);
|
||||||
|
} else {
|
||||||
|
// ERROR: header line too long
|
||||||
|
print("Header line too long");
|
||||||
|
eventDriver.shutdownSocket(client);
|
||||||
|
eventDriver.releaseRef(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
source/eventcore/core.d
Normal file
26
source/eventcore/core.d
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
module eventcore.core;
|
||||||
|
|
||||||
|
public import eventcore.driver;
|
||||||
|
|
||||||
|
import eventcore.epoll;
|
||||||
|
import eventcore.select;
|
||||||
|
|
||||||
|
alias NativeEventDriver = SelectEventDriver;
|
||||||
|
|
||||||
|
@property EventDriver eventDriver()
|
||||||
|
@safe @nogc nothrow {
|
||||||
|
assert(s_driver !is null, "eventcore.core static constructor didn't run!?");
|
||||||
|
return s_driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
static this()
|
||||||
|
{
|
||||||
|
if (!s_driver) s_driver = new NativeEventDriver;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared static this()
|
||||||
|
{
|
||||||
|
s_driver = new NativeEventDriver;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NativeEventDriver s_driver;
|
162
source/eventcore/driver.d
Normal file
162
source/eventcore/driver.d
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
module eventcore.driver;
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
|
||||||
|
import core.time : Duration;
|
||||||
|
import std.socket : Address;
|
||||||
|
|
||||||
|
|
||||||
|
interface EventDriver {
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
//
|
||||||
|
// General functionality
|
||||||
|
//
|
||||||
|
|
||||||
|
/// Releases all resources associated with the driver
|
||||||
|
void dispose();
|
||||||
|
|
||||||
|
/**
|
||||||
|
The number of pending callbacks.
|
||||||
|
|
||||||
|
When this number drops to zero, the event loop can safely be quit. It is
|
||||||
|
guaranteed that no callbacks will be made anymore, unless new callbacks
|
||||||
|
get registered.
|
||||||
|
*/
|
||||||
|
size_t waiterCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Runs the event loop to process a chunk of events.
|
||||||
|
|
||||||
|
This method optionally waits for an event to arrive if none are present
|
||||||
|
in the event queue. The function will return after either the specified
|
||||||
|
timeout has elapsed, or once the event queue has been fully emptied.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
timeout = Maximum amount of time to wait for an event. A duration of
|
||||||
|
zero will cause the function to only process pending events. The
|
||||||
|
the default duration of `Duration.max`, if necessary, will wait
|
||||||
|
indefinitely until an event arrives.
|
||||||
|
*/
|
||||||
|
void processEvents(Duration timeout = Duration.max);
|
||||||
|
|
||||||
|
//
|
||||||
|
// TCP
|
||||||
|
//
|
||||||
|
StreamSocketFD connectStream(scope Address peer_address, ConnectCallback on_connect);
|
||||||
|
StreamListenSocketFD listenStream(scope Address bind_address, AcceptCallback on_accept);
|
||||||
|
void waitForConnections(StreamListenSocketFD sock, AcceptCallback on_accept);
|
||||||
|
|
||||||
|
void setTCPNoDelay(StreamSocketFD socket, bool enable);
|
||||||
|
void readSocket(StreamSocketFD socket, ubyte[] buffer, IOCallback on_read_finish, IOMode mode = IOMode.once);
|
||||||
|
void writeSocket(StreamSocketFD socket, const(ubyte)[] buffer, IOCallback on_write_finish, IOMode mode = IOMode.once);
|
||||||
|
void waitSocketData(StreamSocketFD socket, IOCallback on_data_available);
|
||||||
|
void shutdownSocket(StreamSocketFD socket, bool shut_read = true, bool shut_write = true);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Manual events
|
||||||
|
//
|
||||||
|
EventID createEvent();
|
||||||
|
void triggerEvent(EventID event, bool notify_all = true);
|
||||||
|
EventWaitID waitForEvent(EventID event, EventCallback on_event);
|
||||||
|
void stopWaitingForEvent(EventID event, EventWaitID wait_id);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Timers
|
||||||
|
//
|
||||||
|
TimerID createTimer(TimerCallback callback);
|
||||||
|
void setTimer(TimerID timer, Duration timeout, Duration repeat = Duration.zero);
|
||||||
|
void stopTimer(TimerID timer);
|
||||||
|
bool isTimerPending(TimerID timer);
|
||||||
|
bool isTimerPeriodic(TimerID timer);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Resource ownership
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
Increments the reference count of the given resource.
|
||||||
|
*/
|
||||||
|
void addRef(SocketFD descriptor);
|
||||||
|
/// ditto
|
||||||
|
void addRef(FileFD descriptor);
|
||||||
|
/// ditto
|
||||||
|
void addRef(TimerID descriptor);
|
||||||
|
/// ditto
|
||||||
|
void addRef(EventID descriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Decrements the reference count of the given resource.
|
||||||
|
|
||||||
|
Once the reference count reaches zero, all associated resources will be
|
||||||
|
freed and the descriptor gets invalidated.
|
||||||
|
*/
|
||||||
|
void releaseRef(SocketFD descriptor);
|
||||||
|
/// ditto
|
||||||
|
void releaseRef(FileFD descriptor);
|
||||||
|
/// ditto
|
||||||
|
void releaseRef(TimerID descriptor);
|
||||||
|
/// ditto
|
||||||
|
void releaseRef(EventID descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
alias ConnectCallback = void delegate(StreamSocketFD, ConnectStatus);
|
||||||
|
alias AcceptCallback = void delegate(StreamListenSocketFD, StreamSocketFD);
|
||||||
|
alias IOCallback = void delegate(StreamSocketFD, IOStatus, size_t);
|
||||||
|
alias EventCallback = void delegate(EventID);
|
||||||
|
alias TimerCallback = void delegate(TimerID);
|
||||||
|
|
||||||
|
enum ConnectStatus {
|
||||||
|
connected,
|
||||||
|
refused,
|
||||||
|
timeout,
|
||||||
|
bindFailure,
|
||||||
|
unknownError
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IOMode {
|
||||||
|
immediate, /// Process only as much as possible without waiting
|
||||||
|
once, /// Process as much as possible with a single call
|
||||||
|
all /// Process the full buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum IOStatus {
|
||||||
|
ok, /// The data has been transferred normally
|
||||||
|
disconnected, /// The connection was closed before all data could be transterred
|
||||||
|
error, /// An error occured while transferring the data
|
||||||
|
wouldBlock /// Returned for `IOMode.immediate` when no data is readily readable/writable
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Handle(T, T invalid_value = T.init, int MAGIC = __LINE__) {
|
||||||
|
static if (is(T : Handle!(V, M), V, int M)) alias BaseType = T.BaseType;
|
||||||
|
else alias BaseType = T;
|
||||||
|
|
||||||
|
enum invalid = Handle.init;
|
||||||
|
|
||||||
|
T value = invalid_value;
|
||||||
|
|
||||||
|
this(BaseType value) { this.value = T(value); }
|
||||||
|
|
||||||
|
U opCast(U : Handle!(V, M), V, int M)() {
|
||||||
|
// TODO: verify that U derives from typeof(this)!
|
||||||
|
return U(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
U opCast(U : BaseType)()
|
||||||
|
{
|
||||||
|
return cast(U)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
alias value this;
|
||||||
|
}
|
||||||
|
|
||||||
|
alias FD = Handle!(int, -1);
|
||||||
|
alias SocketFD = Handle!FD;
|
||||||
|
alias StreamSocketFD = Handle!SocketFD;
|
||||||
|
alias StreamListenSocketFD = Handle!SocketFD;
|
||||||
|
alias FileFD = Handle!FD;
|
||||||
|
|
||||||
|
alias TimerID = Handle!int;
|
||||||
|
alias EventID = Handle!int;
|
||||||
|
alias EventWaitID = Handle!int;
|
||||||
|
|
87
source/eventcore/epoll.d
Normal file
87
source/eventcore/epoll.d
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
module eventcore.epoll;
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
|
||||||
|
public import eventcore.posix;
|
||||||
|
import eventcore.internal.utils;
|
||||||
|
|
||||||
|
import core.time : Duration;
|
||||||
|
import core.sys.posix.sys.time : timeval;
|
||||||
|
import core.sys.linux.epoll;
|
||||||
|
|
||||||
|
|
||||||
|
final class EpollEventDriver : PosixEventDriver {
|
||||||
|
private {
|
||||||
|
int m_epoll;
|
||||||
|
epoll_event[] m_events;
|
||||||
|
}
|
||||||
|
|
||||||
|
this()
|
||||||
|
{
|
||||||
|
m_epoll = () @trusted { return epoll_create1(0); } ();
|
||||||
|
m_events.length = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void doProcessEvents(Duration timeout)
|
||||||
|
@trusted {
|
||||||
|
import std.algorithm : min;
|
||||||
|
//assert(Fiber.getThis() is null, "processEvents may not be called from within a fiber!");
|
||||||
|
|
||||||
|
auto ts = timeout.toTimeVal;
|
||||||
|
|
||||||
|
//print("wait %s", m_events.length);
|
||||||
|
auto ret = epoll_wait(m_epoll, m_events.ptr, cast(int)m_events.length, timeout == Duration.max ? -1 : cast(int)min(timeout.total!"msecs", int.max));
|
||||||
|
//print("wait done %s", ret);
|
||||||
|
|
||||||
|
if (ret > 0) {
|
||||||
|
foreach (ref evt; m_events[0 .. ret]) {
|
||||||
|
//print("event %s %s", evt.data.fd, evt.events);
|
||||||
|
auto fd = cast(FD)evt.data.fd;
|
||||||
|
if (evt.events & EPOLLIN) notify!(EventType.read)(fd);
|
||||||
|
if (evt.events & EPOLLOUT) notify!(EventType.write)(fd);
|
||||||
|
if (evt.events & EPOLLERR) notify!(EventType.status)(fd);
|
||||||
|
else if (evt.events & EPOLLHUP) notify!(EventType.status)(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void dispose()
|
||||||
|
{
|
||||||
|
close(m_epoll);
|
||||||
|
}
|
||||||
|
|
||||||
|
override void registerFD(FD fd, EventMask mask)
|
||||||
|
{
|
||||||
|
//print("register %s %s", fd, mask);
|
||||||
|
epoll_event ev;
|
||||||
|
ev.events |= EPOLLET;
|
||||||
|
if (mask & EventMask.read) ev.events |= EPOLLIN;
|
||||||
|
if (mask & EventMask.write) ev.events |= EPOLLOUT;
|
||||||
|
if (mask & EventMask.status) ev.events |= EPOLLERR|EPOLLRDHUP;
|
||||||
|
ev.data.fd = fd;
|
||||||
|
() @trusted { epoll_ctl(m_epoll, EPOLL_CTL_ADD, fd, &ev); } ();
|
||||||
|
}
|
||||||
|
|
||||||
|
override void unregisterFD(FD fd)
|
||||||
|
{
|
||||||
|
() @trusted { epoll_ctl(m_epoll, EPOLL_CTL_DEL, fd, null); } ();
|
||||||
|
}
|
||||||
|
|
||||||
|
override void updateFD(FD fd, EventMask mask)
|
||||||
|
{
|
||||||
|
//print("update %s %s", fd, mask);
|
||||||
|
epoll_event ev;
|
||||||
|
//ev.events = EPOLLONESHOT;
|
||||||
|
if (mask & EventMask.read) ev.events |= EPOLLIN;
|
||||||
|
if (mask & EventMask.write) ev.events |= EPOLLOUT;
|
||||||
|
if (mask & EventMask.status) ev.events |= EPOLLERR|EPOLLRDHUP;
|
||||||
|
ev.data.fd = fd;
|
||||||
|
() @trusted { epoll_ctl(m_epoll, EPOLL_CTL_MOD, fd, &ev); } ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private timeval toTimeVal(Duration dur)
|
||||||
|
{
|
||||||
|
timeval tvdur;
|
||||||
|
dur.split!("seconds", "usecs")(tvdur.tv_sec, tvdur.tv_usec);
|
||||||
|
return tvdur;
|
||||||
|
}
|
112
source/eventcore/internal/utils.d
Normal file
112
source/eventcore/internal/utils.d
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
module eventcore.internal.utils;
|
||||||
|
void print(ARGS...)(string str, ARGS args)
|
||||||
|
@trusted @nogc nothrow {
|
||||||
|
import std.format : formattedWrite;
|
||||||
|
StdoutRange r;
|
||||||
|
scope cb = () {
|
||||||
|
scope (failure) assert(false);
|
||||||
|
(&r).formattedWrite(str, args);
|
||||||
|
};
|
||||||
|
(cast(void delegate() @nogc @safe nothrow)cb)();
|
||||||
|
r.put('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StdoutRange {
|
||||||
|
@safe: @nogc: nothrow:
|
||||||
|
import core.stdc.stdio;
|
||||||
|
|
||||||
|
void put(string str)
|
||||||
|
{
|
||||||
|
() @trusted { fwrite(str.ptr, str.length, 1, stdout); } ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(char ch)
|
||||||
|
{
|
||||||
|
() @trusted { fputc(ch, stdout); } ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChoppedVector(T, size_t CHUNK_SIZE = 16*64*1024/nextPOT(T.sizeof)) {
|
||||||
|
static assert(nextPOT(CHUNK_SIZE) == CHUNK_SIZE,
|
||||||
|
"CHUNK_SIZE must be a power of two for performance reasons.");
|
||||||
|
|
||||||
|
@safe: @nogc: nothrow:
|
||||||
|
import core.stdc.stdlib : calloc, free, malloc, realloc;
|
||||||
|
import std.traits : hasElaborateDestructor;
|
||||||
|
|
||||||
|
static assert(!hasElaborateDestructor!T);
|
||||||
|
|
||||||
|
alias chunkSize = CHUNK_SIZE;
|
||||||
|
|
||||||
|
private {
|
||||||
|
alias Chunk = T[chunkSize];
|
||||||
|
alias ChunkPtr = Chunk*;
|
||||||
|
ChunkPtr[] m_chunks;
|
||||||
|
size_t m_chunkCount;
|
||||||
|
size_t m_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@property size_t length() const { return m_length; }
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
() @trusted {
|
||||||
|
foreach (i; 0 .. m_chunkCount)
|
||||||
|
free(m_chunks[i]);
|
||||||
|
free(m_chunks.ptr);
|
||||||
|
} ();
|
||||||
|
m_chunkCount = 0;
|
||||||
|
m_length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref T opIndex(size_t index)
|
||||||
|
{
|
||||||
|
auto chunk = index / chunkSize;
|
||||||
|
auto subidx = index % chunkSize;
|
||||||
|
if (index >= m_length) m_length = index+1;
|
||||||
|
reserveChunk(chunk);
|
||||||
|
return (*m_chunks[chunk])[subidx];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reserveChunk(size_t chunkidx)
|
||||||
|
{
|
||||||
|
if (m_chunks.length <= chunkidx) {
|
||||||
|
auto l = m_chunks.length == 0 ? 64 : m_chunks.length;
|
||||||
|
while (l <= chunkidx) l *= 2;
|
||||||
|
() @trusted {
|
||||||
|
auto newptr = cast(ChunkPtr*)realloc(m_chunks.ptr, l * ChunkPtr.length);
|
||||||
|
m_chunks = newptr[0 .. l];
|
||||||
|
} ();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (m_chunkCount <= chunkidx) {
|
||||||
|
() @trusted { m_chunks[m_chunkCount++] = cast(ChunkPtr)calloc(chunkSize, T.sizeof); } ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private size_t nextPOT(size_t n)
|
||||||
|
{
|
||||||
|
foreach_reverse (i; 0 .. size_t.sizeof*8) {
|
||||||
|
size_t ni = cast(size_t)1 << i;
|
||||||
|
if (n & ni) {
|
||||||
|
return n & (ni-1) ? ni << 1 : ni;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
assert(nextPOT(1) == 1);
|
||||||
|
assert(nextPOT(2) == 2);
|
||||||
|
assert(nextPOT(3) == 4);
|
||||||
|
assert(nextPOT(4) == 4);
|
||||||
|
assert(nextPOT(5) == 8);
|
||||||
|
}
|
603
source/eventcore/posix.d
Normal file
603
source/eventcore/posix.d
Normal file
|
@ -0,0 +1,603 @@
|
||||||
|
module eventcore.posix;
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
|
||||||
|
public import eventcore.driver;
|
||||||
|
import eventcore.timer;
|
||||||
|
import eventcore.internal.utils;
|
||||||
|
|
||||||
|
import std.socket : Address, AddressFamily, UnknownAddress;
|
||||||
|
import core.sys.posix.netinet.in_;
|
||||||
|
import core.sys.posix.netinet.tcp;
|
||||||
|
import core.sys.posix.unistd : close;
|
||||||
|
import core.stdc.errno : errno, EAGAIN, EINPROGRESS;
|
||||||
|
import core.sys.posix.fcntl;
|
||||||
|
version (Windows) import core.sys.windows.winsock;
|
||||||
|
|
||||||
|
|
||||||
|
private long currStdTime()
|
||||||
|
{
|
||||||
|
import std.datetime : Clock;
|
||||||
|
scope (failure) assert(false);
|
||||||
|
return Clock.currStdTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class PosixEventDriver : EventDriver {
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
|
||||||
|
private {
|
||||||
|
ChoppedVector!FDSlot m_fds;
|
||||||
|
size_t m_waiterCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin DefaultTimerImpl!();
|
||||||
|
|
||||||
|
protected int maxFD() const { return cast(int)m_fds.length; }
|
||||||
|
|
||||||
|
@property size_t waiterCount() const { return m_waiterCount; }
|
||||||
|
|
||||||
|
final override void processEvents(Duration timeout)
|
||||||
|
{
|
||||||
|
import std.algorithm : min;
|
||||||
|
import core.time : seconds;
|
||||||
|
|
||||||
|
if (timeout <= 0.seconds) {
|
||||||
|
doProcessEvents(0.seconds);
|
||||||
|
processTimers(currStdTime);
|
||||||
|
} else {
|
||||||
|
long now = currStdTime;
|
||||||
|
do {
|
||||||
|
auto nextto = min(getNextTimeout(now), timeout);
|
||||||
|
doProcessEvents(nextto);
|
||||||
|
long prev_step = now;
|
||||||
|
now = currStdTime;
|
||||||
|
processTimers(now);
|
||||||
|
if (timeout != Duration.max)
|
||||||
|
timeout -= (now - prev_step).hnsecs;
|
||||||
|
} while (timeout > 0.seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doProcessEvents(Duration dur);
|
||||||
|
abstract void dispose();
|
||||||
|
|
||||||
|
final override StreamSocketFD connectStream(scope Address address, ConnectCallback on_connect)
|
||||||
|
{
|
||||||
|
auto sock = cast(StreamSocketFD)createSocket(address.addressFamily);
|
||||||
|
if (sock == -1) return StreamSocketFD.invalid;
|
||||||
|
|
||||||
|
void invalidateSocket() @nogc @trusted nothrow { close(sock); sock = StreamSocketFD.invalid; }
|
||||||
|
|
||||||
|
scope bind_addr = new UnknownAddress;
|
||||||
|
bind_addr.name.sa_family = cast(ushort)address.addressFamily;
|
||||||
|
bind_addr.name.sa_data[] = 0;
|
||||||
|
if (() @trusted { return bind(sock, bind_addr.name, bind_addr.nameLen); } () != 0) {
|
||||||
|
invalidateSocket();
|
||||||
|
on_connect(sock, ConnectStatus.bindFailure);
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerFD(sock, EventMask.read|EventMask.write|EventMask.status);
|
||||||
|
|
||||||
|
auto ret = () @trusted { return connect(sock, address.name, address.nameLen); } ();
|
||||||
|
if (ret == 0) {
|
||||||
|
on_connect(sock, ConnectStatus.connected);
|
||||||
|
} else {
|
||||||
|
auto err = errno;
|
||||||
|
if (err == EINPROGRESS) {
|
||||||
|
with (m_fds[sock]) {
|
||||||
|
connectCallback = on_connect;
|
||||||
|
}
|
||||||
|
startNotify!(EventType.write)(sock, &onConnect);
|
||||||
|
} else {
|
||||||
|
unregisterFD(sock);
|
||||||
|
invalidateSocket();
|
||||||
|
on_connect(sock, ConnectStatus.unknownError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addFD(sock);
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onConnect(FD sock)
|
||||||
|
{
|
||||||
|
stopNotify!(EventType.status)(sock);
|
||||||
|
stopNotify!(EventType.write)(sock);
|
||||||
|
m_fds[sock].connectCallback(cast(StreamSocketFD)sock, ConnectStatus.connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onConnectError(FD sock)
|
||||||
|
{
|
||||||
|
stopNotify!(EventType.status)(sock);
|
||||||
|
stopNotify!(EventType.write)(sock);
|
||||||
|
// FIXME: determine the correct kind of error!
|
||||||
|
m_fds[sock].connectCallback(cast(StreamSocketFD)sock, ConnectStatus.refused);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override StreamListenSocketFD listenStream(scope Address address, AcceptCallback on_accept)
|
||||||
|
{
|
||||||
|
auto sock = cast(StreamListenSocketFD)createSocket(address.addressFamily);
|
||||||
|
|
||||||
|
void invalidateSocket() @nogc @trusted nothrow { close(sock); sock = StreamSocketFD.invalid; }
|
||||||
|
|
||||||
|
() @trusted {
|
||||||
|
int tmp_reuse = 1;
|
||||||
|
// FIXME: error handling!
|
||||||
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &tmp_reuse, tmp_reuse.sizeof) != 0) {
|
||||||
|
invalidateSocket();
|
||||||
|
} else if (bind(sock, address.name, address.nameLen) != 0) {
|
||||||
|
invalidateSocket();
|
||||||
|
} else if (listen(sock, 128) != 0) {
|
||||||
|
invalidateSocket();
|
||||||
|
} else { scope (failure) assert(false); import std.stdio; writeln("Success!"); }
|
||||||
|
} ();
|
||||||
|
|
||||||
|
if (on_accept && sock != StreamListenSocketFD.invalid)
|
||||||
|
waitForConnections(sock, on_accept);
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void waitForConnections(StreamListenSocketFD sock, AcceptCallback on_accept)
|
||||||
|
{
|
||||||
|
registerFD(sock, EventMask.read);
|
||||||
|
addFD(sock);
|
||||||
|
m_fds[sock].acceptCallback = on_accept;
|
||||||
|
startNotify!(EventType.read)(sock, &onAccept);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAccept(FD listenfd)
|
||||||
|
{
|
||||||
|
scope addr = new UnknownAddress;
|
||||||
|
foreach (i; 0 .. 20) {
|
||||||
|
int sockfd;
|
||||||
|
uint addr_len = addr.nameLen;
|
||||||
|
() @trusted { sockfd = accept(listenfd, addr.name, &addr_len); } ();
|
||||||
|
if (sockfd == -1) break;
|
||||||
|
() @trusted { fcntl(sockfd, F_SETFL, O_NONBLOCK, 1); } ();
|
||||||
|
registerFD(cast(FD)sockfd, EventMask.read|EventMask.write|EventMask.status);
|
||||||
|
addFD(cast(FD)sockfd);
|
||||||
|
//print("accept %d", sockfd);
|
||||||
|
m_fds[listenfd].acceptCallback(cast(StreamListenSocketFD)listenfd, cast(StreamSocketFD)sockfd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void setTCPNoDelay(StreamSocketFD socket, bool enable)
|
||||||
|
{
|
||||||
|
int opt = enable;
|
||||||
|
() @trusted { setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, cast(char*)&opt, opt.sizeof); } ();
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void readSocket(StreamSocketFD socket, ubyte[] buffer, IOCallback on_read_finish, IOMode mode = IOMode.all)
|
||||||
|
{
|
||||||
|
sizediff_t ret;
|
||||||
|
() @trusted { ret = recv(socket, buffer.ptr, buffer.length, 0); } ();
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
auto err = errno;
|
||||||
|
if (err != EAGAIN) {
|
||||||
|
print("sock error %s!", err);
|
||||||
|
on_read_finish(socket, IOStatus.error, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
|
||||||
|
if (ret == 0) {
|
||||||
|
on_read_finish(socket, IOStatus.disconnected, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0 && mode == IOMode.immediate) {
|
||||||
|
on_read_finish(socket, IOStatus.wouldBlock, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret > 0) {
|
||||||
|
bytes_read += ret;
|
||||||
|
buffer = buffer[bytes_read .. $];
|
||||||
|
if (mode != IOMode.all || buffer.length == 0) {
|
||||||
|
on_read_finish(socket, IOStatus.ok, bytes_read);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with (m_fds[socket]) {
|
||||||
|
readCallback = on_read_finish;
|
||||||
|
readMode = mode;
|
||||||
|
bytesRead = ret > 0 ? ret : 0;
|
||||||
|
readBuffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
startNotify!(EventType.read)(socket, &onSocketRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSocketRead(FD fd)
|
||||||
|
{
|
||||||
|
auto slot = &m_fds[fd];
|
||||||
|
auto socket = cast(StreamSocketFD)fd;
|
||||||
|
|
||||||
|
void finalize()(IOStatus status)
|
||||||
|
{
|
||||||
|
stopNotify!(EventType.read)(socket);
|
||||||
|
//m_fds[fd].readBuffer = null;
|
||||||
|
slot.readCallback(socket, status, slot.bytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
sizediff_t ret;
|
||||||
|
() @trusted { ret = recv(socket, slot.readBuffer.ptr, slot.readBuffer.length, 0); } ();
|
||||||
|
if (ret < 0) {
|
||||||
|
auto err = errno;
|
||||||
|
if (err != EAGAIN) {
|
||||||
|
finalize(IOStatus.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 0) {
|
||||||
|
finalize(IOStatus.disconnected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret > 0) {
|
||||||
|
slot.bytesRead += ret;
|
||||||
|
slot.readBuffer = slot.readBuffer[ret .. $];
|
||||||
|
if (slot.readMode != IOMode.all || slot.readBuffer.length == 0) {
|
||||||
|
finalize(IOStatus.ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void writeSocket(StreamSocketFD socket, const(ubyte)[] buffer, IOCallback on_write_finish, IOMode mode = IOMode.all)
|
||||||
|
{
|
||||||
|
sizediff_t ret;
|
||||||
|
() @trusted { ret = send(socket, buffer.ptr, buffer.length, 0); } ();
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
auto err = errno;
|
||||||
|
if (err != EAGAIN) {
|
||||||
|
on_write_finish(socket, IOStatus.error, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_written = 0;
|
||||||
|
|
||||||
|
if (ret == 0) {
|
||||||
|
on_write_finish(socket, IOStatus.disconnected, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0 && mode == IOMode.immediate) {
|
||||||
|
on_write_finish(socket, IOStatus.wouldBlock, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret > 0) {
|
||||||
|
bytes_written += ret;
|
||||||
|
buffer = buffer[ret .. $];
|
||||||
|
if (mode != IOMode.all || buffer.length == 0) {
|
||||||
|
on_write_finish(socket, IOStatus.ok, bytes_written);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with (m_fds[socket]) {
|
||||||
|
writeCallback = on_write_finish;
|
||||||
|
writeMode = mode;
|
||||||
|
bytesWritten = ret > 0 ? ret : 0;
|
||||||
|
writeBuffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
startNotify!(EventType.write)(socket, &onSocketWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSocketWrite(FD fd)
|
||||||
|
{
|
||||||
|
auto slot = &m_fds[fd];
|
||||||
|
auto socket = cast(StreamSocketFD)fd;
|
||||||
|
|
||||||
|
sizediff_t ret;
|
||||||
|
() @trusted { ret = send(socket, slot.writeBuffer.ptr, slot.writeBuffer.length, 0); } ();
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
auto err = errno;
|
||||||
|
if (err != EAGAIN) {
|
||||||
|
stopNotify!(EventType.write)(socket);
|
||||||
|
slot.readCallback(socket, IOStatus.error, slot.bytesRead);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 0) {
|
||||||
|
stopNotify!(EventType.write)(socket);
|
||||||
|
slot.writeCallback(cast(StreamSocketFD)socket, IOStatus.disconnected, slot.bytesWritten);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret > 0) {
|
||||||
|
slot.bytesWritten += ret;
|
||||||
|
slot.writeBuffer = slot.writeBuffer[ret .. $];
|
||||||
|
if (slot.writeMode != IOMode.all || slot.writeBuffer.length == 0) {
|
||||||
|
stopNotify!(EventType.write)(socket);
|
||||||
|
slot.writeCallback(cast(StreamSocketFD)socket, IOStatus.ok, slot.bytesWritten);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void waitSocketData(StreamSocketFD socket, IOCallback on_data_available)
|
||||||
|
{
|
||||||
|
sizediff_t ret;
|
||||||
|
ubyte dummy;
|
||||||
|
() @trusted { ret = recv(socket, &dummy, 1, MSG_PEEK); } ();
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
auto err = errno;
|
||||||
|
if (err != EAGAIN) {
|
||||||
|
on_data_available(socket, IOStatus.error, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
|
||||||
|
if (ret == 0) {
|
||||||
|
on_data_available(socket, IOStatus.disconnected, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret > 0) {
|
||||||
|
on_data_available(socket, IOStatus.ok, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
with (m_fds[socket]) {
|
||||||
|
readCallback = on_data_available;
|
||||||
|
readMode = IOMode.once;
|
||||||
|
bytesRead = 0;
|
||||||
|
readBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
startNotify!(EventType.read)(socket, &onSocketDataAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSocketDataAvailable(FD fd)
|
||||||
|
{
|
||||||
|
auto slot = &m_fds[fd];
|
||||||
|
auto socket = cast(StreamSocketFD)fd;
|
||||||
|
|
||||||
|
void finalize()(IOStatus status)
|
||||||
|
{
|
||||||
|
stopNotify!(EventType.read)(socket);
|
||||||
|
//m_fds[fd].readBuffer = null;
|
||||||
|
slot.readCallback(socket, status, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sizediff_t ret;
|
||||||
|
ubyte tmp;
|
||||||
|
() @trusted { ret = recv(socket, &tmp, 1, MSG_PEEK); } ();
|
||||||
|
if (ret < 0) {
|
||||||
|
auto err = errno;
|
||||||
|
if (err != EAGAIN) finalize(IOStatus.error);
|
||||||
|
} else finalize(ret ? IOStatus.ok : IOStatus.disconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void shutdownSocket(StreamSocketFD socket, bool shut_read, bool shut_write)
|
||||||
|
{
|
||||||
|
// TODO!
|
||||||
|
}
|
||||||
|
|
||||||
|
final override EventID createEvent()
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void triggerEvent(EventID event, bool notify_all = true)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override EventWaitID waitForEvent(EventID event, EventCallback on_event)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void stopWaitingForEvent(EventID event, EventWaitID wait_id)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void addRef(SocketFD fd)
|
||||||
|
{
|
||||||
|
auto pfd = &m_fds[fd];
|
||||||
|
assert(pfd.refCount > 0);
|
||||||
|
m_fds[fd].refCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void addRef(FileFD descriptor)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void addRef(EventID descriptor)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void releaseRef(SocketFD fd)
|
||||||
|
{
|
||||||
|
auto pfd = &m_fds[fd];
|
||||||
|
assert(pfd.refCount > 0);
|
||||||
|
if (--m_fds[fd].refCount == 0) {
|
||||||
|
unregisterFD(fd);
|
||||||
|
clearFD(fd);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void releaseRef(FileFD descriptor)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void releaseRef(TimerID descriptor)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void releaseRef(EventID descriptor)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Registers the FD for general notification reception.
|
||||||
|
protected abstract void registerFD(FD fd, EventMask mask);
|
||||||
|
/// Unregisters the FD for general notification reception.
|
||||||
|
protected abstract void unregisterFD(FD fd);
|
||||||
|
/// Updates the event mask to use for listening for notifications.
|
||||||
|
protected abstract void updateFD(FD fd, EventMask mask);
|
||||||
|
|
||||||
|
final protected void enumerateFDs(EventType evt)(scope FDEnumerateCallback del)
|
||||||
|
{
|
||||||
|
// TODO: optimize!
|
||||||
|
foreach (i; 0 .. cast(int)m_fds.length)
|
||||||
|
if (m_fds[i].callback[evt])
|
||||||
|
del(cast(FD)i);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected void notify(EventType evt)(FD fd)
|
||||||
|
{
|
||||||
|
//assert(m_fds[fd].callback[evt] !is null, "Notifying FD which is not listening for event.");
|
||||||
|
if (m_fds[fd].callback[evt])
|
||||||
|
m_fds[fd].callback[evt](fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startNotify(EventType evt)(FD fd, FDSlotCallback callback)
|
||||||
|
{
|
||||||
|
assert(m_fds[fd].callback[evt] is null, "Waiting for event which is already being waited for.");
|
||||||
|
m_fds[fd].callback[evt] = callback;
|
||||||
|
assert(m_fds[0].callback[evt] is null);
|
||||||
|
m_waiterCount++;
|
||||||
|
updateFD(fd, m_fds[fd].eventMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopNotify(EventType evt)(FD fd)
|
||||||
|
{
|
||||||
|
assert(m_fds[fd].callback[evt] !is null, "Stopping waiting for event which is not being waited for.");
|
||||||
|
m_fds[fd].callback[evt] = null;
|
||||||
|
m_waiterCount--;
|
||||||
|
updateFD(fd, m_fds[fd].eventMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SocketFD createSocket(AddressFamily family)
|
||||||
|
{
|
||||||
|
int sock;
|
||||||
|
() @trusted { sock = socket(family, SOCK_STREAM, 0); } ();
|
||||||
|
if (sock == -1) return SocketFD.invalid;
|
||||||
|
int on = 1;
|
||||||
|
() @trusted { fcntl(sock, F_SETFL, O_NONBLOCK, on); } ();
|
||||||
|
return cast(SocketFD)sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFD(FD fd)
|
||||||
|
{
|
||||||
|
m_fds[fd].refCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearFD(FD fd)
|
||||||
|
{
|
||||||
|
m_fds[fd] = FDSlot.init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias FDEnumerateCallback = void delegate(FD);
|
||||||
|
|
||||||
|
alias FDSlotCallback = void delegate(FD);
|
||||||
|
|
||||||
|
private struct FDSlot {
|
||||||
|
FDSlotCallback[EventType.max+1] callback;
|
||||||
|
|
||||||
|
uint refCount;
|
||||||
|
|
||||||
|
size_t bytesRead;
|
||||||
|
ubyte[] readBuffer;
|
||||||
|
IOMode readMode;
|
||||||
|
IOCallback readCallback;
|
||||||
|
|
||||||
|
size_t bytesWritten;
|
||||||
|
const(ubyte)[] writeBuffer;
|
||||||
|
IOMode writeMode;
|
||||||
|
IOCallback writeCallback;
|
||||||
|
|
||||||
|
ConnectCallback connectCallback;
|
||||||
|
AcceptCallback acceptCallback;
|
||||||
|
|
||||||
|
@property EventMask eventMask() const nothrow {
|
||||||
|
EventMask ret = cast(EventMask)0;
|
||||||
|
if (callback[EventType.read] !is null) ret |= EventMask.read;
|
||||||
|
if (callback[EventType.write] !is null) ret |= EventMask.write;
|
||||||
|
if (callback[EventType.status] !is null) ret |= EventMask.status;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EventType {
|
||||||
|
read,
|
||||||
|
write,
|
||||||
|
status
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EventMask {
|
||||||
|
read = 1<<0,
|
||||||
|
write = 1<<1,
|
||||||
|
status = 1<<2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*version (Windows) {
|
||||||
|
import std.c.windows.windows;
|
||||||
|
import std.c.windows.winsock;
|
||||||
|
|
||||||
|
alias EWOULDBLOCK = WSAEWOULDBLOCK;
|
||||||
|
|
||||||
|
extern(System) DWORD FormatMessageW(DWORD dwFlags, const(void)* lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, void* Arguments);
|
||||||
|
|
||||||
|
class WSAErrorException : Exception {
|
||||||
|
int error;
|
||||||
|
|
||||||
|
this(string message, string file = __FILE__, size_t line = __LINE__)
|
||||||
|
{
|
||||||
|
error = WSAGetLastError();
|
||||||
|
this(message, error, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
this(string message, int error, string file = __FILE__, size_t line = __LINE__)
|
||||||
|
{
|
||||||
|
import std.string : format;
|
||||||
|
ushort* errmsg;
|
||||||
|
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), cast(LPWSTR)&errmsg, 0, null);
|
||||||
|
size_t len = 0;
|
||||||
|
while (errmsg[len]) len++;
|
||||||
|
auto errmsgd = (cast(wchar[])errmsg[0 .. len]).idup;
|
||||||
|
LocalFree(errmsg);
|
||||||
|
super(format("%s: %s (%s)", message, errmsgd, error), file, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias SystemSocketException = WSAErrorException;
|
||||||
|
} else {
|
||||||
|
import std.exception : ErrnoException;
|
||||||
|
alias SystemSocketException = ErrnoException;
|
||||||
|
}
|
||||||
|
|
||||||
|
T socketEnforce(T)(T value, lazy string msg = null, string file = __FILE__, size_t line = __LINE__)
|
||||||
|
{
|
||||||
|
import std.exception : enforceEx;
|
||||||
|
return enforceEx!SystemSocketException(value, msg, file, line);
|
||||||
|
}*/
|
76
source/eventcore/select.d
Normal file
76
source/eventcore/select.d
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
module eventcore.select;
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
|
||||||
|
public import eventcore.posix;
|
||||||
|
import eventcore.internal.utils;
|
||||||
|
|
||||||
|
import core.time : Duration;
|
||||||
|
import core.sys.posix.sys.time : timeval;
|
||||||
|
import core.sys.posix.sys.select;
|
||||||
|
|
||||||
|
|
||||||
|
final class SelectEventDriver : PosixEventDriver {
|
||||||
|
override void doProcessEvents(Duration timeout)
|
||||||
|
{
|
||||||
|
//assert(Fiber.getThis() is null, "processEvents may not be called from within a fiber!");
|
||||||
|
//scope (failure) assert(false); import std.stdio; writefln("%.3f: process %s ms", Clock.currAppTick.usecs * 1e-3, timeout.total!"msecs");
|
||||||
|
//scope (success) writefln("%.3f: process out", Clock.currAppTick.usecs * 1e-3);
|
||||||
|
|
||||||
|
auto ts = timeout.toTimeVal;
|
||||||
|
|
||||||
|
fd_set readfds, writefds, statusfds;
|
||||||
|
|
||||||
|
() @trusted {
|
||||||
|
FD_ZERO(&readfds);
|
||||||
|
FD_ZERO(&writefds);
|
||||||
|
FD_ZERO(&statusfds);
|
||||||
|
} ();
|
||||||
|
enumerateFDs!(EventType.read)((fd) @trusted { FD_SET(fd, &readfds); });
|
||||||
|
enumerateFDs!(EventType.write)((fd) @trusted { FD_SET(fd, &writefds); });
|
||||||
|
enumerateFDs!(EventType.status)((fd) @trusted { FD_SET(fd, &statusfds); });
|
||||||
|
|
||||||
|
//print("Wait for event...");
|
||||||
|
//writefln("%.3f: select in", Clock.currAppTick.usecs * 1e-3);
|
||||||
|
auto ret = () @trusted { return select(this.maxFD+1, &readfds, &writefds, &statusfds, timeout == Duration.max ? null : &ts); } ();
|
||||||
|
//writefln("%.3f: select out", Clock.currAppTick.usecs * 1e-3);
|
||||||
|
//print("Done wait for event...");
|
||||||
|
if (ret > 0) {
|
||||||
|
enumerateFDs!(EventType.read)((fd) @trusted {
|
||||||
|
if (FD_ISSET(fd, &readfds))
|
||||||
|
notify!(EventType.read)(fd);
|
||||||
|
});
|
||||||
|
enumerateFDs!(EventType.write)((fd) @trusted {
|
||||||
|
if (FD_ISSET(fd, &writefds))
|
||||||
|
notify!(EventType.write)(fd);
|
||||||
|
});
|
||||||
|
enumerateFDs!(EventType.status)((fd) @trusted {
|
||||||
|
if (FD_ISSET(fd, &statusfds))
|
||||||
|
notify!(EventType.status)(fd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override void registerFD(FD fd, EventMask mask)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
override void unregisterFD(FD fd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
override void updateFD(FD fd, EventMask mask)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private timeval toTimeVal(Duration dur)
|
||||||
|
{
|
||||||
|
timeval tvdur;
|
||||||
|
dur.split!("seconds", "usecs")(tvdur.tv_sec, tvdur.tv_usec);
|
||||||
|
return tvdur;
|
||||||
|
}
|
147
source/eventcore/timer.d
Normal file
147
source/eventcore/timer.d
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
module eventcore.timer;
|
||||||
|
|
||||||
|
import eventcore.driver;
|
||||||
|
|
||||||
|
|
||||||
|
mixin template DefaultTimerImpl() {
|
||||||
|
import std.experimental.allocator.building_blocks.free_list;
|
||||||
|
import std.experimental.allocator.building_blocks.region;
|
||||||
|
import std.experimental.allocator.mallocator;
|
||||||
|
import std.experimental.allocator : dispose, make;
|
||||||
|
import std.container.array;
|
||||||
|
import std.datetime : Clock;
|
||||||
|
import std.range : SortedRange, assumeSorted, take;
|
||||||
|
import core.time : hnsecs;
|
||||||
|
|
||||||
|
private {
|
||||||
|
static FreeList!(Mallocator, TimerSlot.sizeof) ms_allocator;
|
||||||
|
TimerSlot*[TimerID] m_timers;
|
||||||
|
Array!(TimerSlot*) m_timerQueue;
|
||||||
|
TimerID m_lastTimerID;
|
||||||
|
TimerSlot*[] m_firedTimers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static this()
|
||||||
|
{
|
||||||
|
ms_allocator.parent = Mallocator.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected Duration getNextTimeout(long stdtime)
|
||||||
|
{
|
||||||
|
return m_timerQueue.length ? (m_timerQueue.front.timeout - stdtime).hnsecs : Duration.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected void processTimers(long stdtime)
|
||||||
|
@trusted {
|
||||||
|
assert(m_firedTimers.length == 0);
|
||||||
|
if (m_timerQueue.empty) return;
|
||||||
|
|
||||||
|
TimerSlot ts = void;
|
||||||
|
ts.timeout = stdtime+1;
|
||||||
|
auto fired = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).lowerBound(&ts);
|
||||||
|
foreach (tm; fired) {
|
||||||
|
if (tm.repeatDuration > 0) {
|
||||||
|
do tm.timeout += tm.repeatDuration;
|
||||||
|
while (tm.timeout <= stdtime);
|
||||||
|
auto tail = m_timerQueue[fired.length .. $].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(tm);
|
||||||
|
try m_timerQueue.insertBefore(tail.release, tm);
|
||||||
|
catch (Exception e) { print("Failed to insert timer: %s", e.msg); }
|
||||||
|
} else tm.pending = false;
|
||||||
|
m_firedTimers ~= tm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this isn't yet verified to work under all circumstances
|
||||||
|
auto elems = m_timerQueue[0 .. fired.length];
|
||||||
|
scope (failure) assert(false);
|
||||||
|
m_timerQueue.linearRemove(elems);
|
||||||
|
|
||||||
|
foreach (tm; m_firedTimers)
|
||||||
|
tm.callback(tm.id);
|
||||||
|
|
||||||
|
m_firedTimers.length = 0;
|
||||||
|
m_firedTimers.assumeSafeAppend();
|
||||||
|
}
|
||||||
|
|
||||||
|
final override TimerID createTimer(TimerCallback callback)
|
||||||
|
@trusted {
|
||||||
|
auto id = cast(TimerID)(m_lastTimerID + 1);
|
||||||
|
TimerSlot* tm;
|
||||||
|
try tm = ms_allocator.make!TimerSlot;
|
||||||
|
catch (Exception e) return TimerID.invalid;
|
||||||
|
assert(tm !is null);
|
||||||
|
tm.id = id;
|
||||||
|
tm.refCount = 1;
|
||||||
|
tm.callback = callback;
|
||||||
|
m_timers[id] = tm;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void setTimer(TimerID timer, Duration timeout, Duration repeat)
|
||||||
|
@trusted {
|
||||||
|
scope (failure) assert(false);
|
||||||
|
auto tm = m_timers[timer];
|
||||||
|
if (tm.pending) stopTimer(timer);
|
||||||
|
tm.timeout = Clock.currStdTime + timeout.total!"hnsecs";
|
||||||
|
tm.repeatDuration = repeat.total!"hnsecs";
|
||||||
|
tm.pending = true;
|
||||||
|
|
||||||
|
auto largerRange = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(tm);
|
||||||
|
try m_timerQueue.insertBefore(largerRange.release, tm);
|
||||||
|
catch (Exception e) { print("Failed to insert timer: %s", e.msg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void stopTimer(TimerID timer)
|
||||||
|
@trusted {
|
||||||
|
auto tm = m_timers[timer];
|
||||||
|
if (!tm.pending) return;
|
||||||
|
tm.pending = false;
|
||||||
|
tm.callback = null;
|
||||||
|
|
||||||
|
TimerSlot cmp = void;
|
||||||
|
cmp.timeout = tm.timeout-1;
|
||||||
|
auto upper = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(&cmp);
|
||||||
|
assert(!upper.empty);
|
||||||
|
while (!upper.empty) {
|
||||||
|
assert(upper.front.timeout == tm.timeout);
|
||||||
|
if (upper.front is tm) {
|
||||||
|
scope (failure) assert(false);
|
||||||
|
m_timerQueue.linearRemove(upper.release.take(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final override bool isTimerPending(TimerID descriptor)
|
||||||
|
{
|
||||||
|
return m_timers[descriptor].pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
final override bool isTimerPeriodic(TimerID descriptor)
|
||||||
|
{
|
||||||
|
return m_timers[descriptor].repeatDuration > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void addRef(TimerID descriptor)
|
||||||
|
{
|
||||||
|
m_timers[descriptor].refCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
final override void releaseRef(TimerID descriptor)
|
||||||
|
{
|
||||||
|
auto tm = m_timers[descriptor];
|
||||||
|
if (!--tm.refCount) {
|
||||||
|
if (tm.pending) stopTimer(tm.id);
|
||||||
|
m_timers.remove(descriptor);
|
||||||
|
() @trusted { scope (failure) assert(false); ms_allocator.dispose(tm); } ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimerSlot {
|
||||||
|
TimerID id;
|
||||||
|
uint refCount;
|
||||||
|
bool pending;
|
||||||
|
long timeout; // stdtime
|
||||||
|
long repeatDuration;
|
||||||
|
TimerCallback callback;
|
||||||
|
}
|
Loading…
Reference in a new issue