Make file and pipe closing asynchronous.
CloseHandle can block for extended periods of time in certain cases, causing the whole thread to be blocked otherwise.
This commit is contained in:
parent
86ee5c9477
commit
85dc8d54e6
|
@ -16,12 +16,12 @@
|
||||||
cancellation function returns, or afterwards.
|
cancellation function returns, or afterwards.
|
||||||
*/
|
*/
|
||||||
module eventcore.driver;
|
module eventcore.driver;
|
||||||
@safe: /*@nogc:*/ nothrow:
|
|
||||||
|
|
||||||
import core.time : Duration;
|
import core.time : Duration;
|
||||||
import std.process : StdProcessConfig = Config;
|
import std.process : StdProcessConfig = Config;
|
||||||
import std.socket : Address;
|
import std.socket : Address;
|
||||||
import std.stdint : intptr_t;
|
import std.stdint : intptr_t;
|
||||||
|
import std.typecons : Tuple;
|
||||||
import std.variant : Algebraic;
|
import std.variant : Algebraic;
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,14 +71,13 @@ interface EventDriver {
|
||||||
/** Provides generic event loop control.
|
/** Provides generic event loop control.
|
||||||
*/
|
*/
|
||||||
interface EventDriverCore {
|
interface EventDriverCore {
|
||||||
@safe: /*@nogc:*/ nothrow:
|
|
||||||
/** The number of pending callbacks.
|
/** The number of pending callbacks.
|
||||||
|
|
||||||
When this number drops to zero, the event loop can safely be quit. It is
|
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
|
guaranteed that no callbacks will be made anymore, unless new callbacks
|
||||||
get registered.
|
get registered.
|
||||||
*/
|
*/
|
||||||
size_t waiterCount();
|
size_t waiterCount() @safe nothrow;
|
||||||
|
|
||||||
/** Runs the event loop to process a chunk of events.
|
/** Runs the event loop to process a chunk of events.
|
||||||
|
|
||||||
|
@ -92,7 +91,7 @@ interface EventDriverCore {
|
||||||
duration of `Duration.max`, if necessary, will wait indefinitely
|
duration of `Duration.max`, if necessary, will wait indefinitely
|
||||||
until an event arrives.
|
until an event arrives.
|
||||||
*/
|
*/
|
||||||
ExitReason processEvents(Duration timeout);
|
ExitReason processEvents(Duration timeout) @safe nothrow;
|
||||||
|
|
||||||
/** Causes `processEvents` to return with `ExitReason.exited` as soon as
|
/** Causes `processEvents` to return with `ExitReason.exited` as soon as
|
||||||
possible.
|
possible.
|
||||||
|
@ -101,7 +100,7 @@ interface EventDriverCore {
|
||||||
so that it returns immediately. If no call is in progress, the next call
|
so that it returns immediately. If no call is in progress, the next call
|
||||||
to `processEvents` will immediately return with `ExitReason.exited`.
|
to `processEvents` will immediately return with `ExitReason.exited`.
|
||||||
*/
|
*/
|
||||||
void exit();
|
void exit() @safe nothrow;
|
||||||
|
|
||||||
/** Resets the exit flag.
|
/** Resets the exit flag.
|
||||||
|
|
||||||
|
@ -110,26 +109,28 @@ interface EventDriverCore {
|
||||||
`processEvents`, the next call to `processEvents` will return with
|
`processEvents`, the next call to `processEvents` will return with
|
||||||
`ExitCode.exited` immediately. This function can be used to avoid this.
|
`ExitCode.exited` immediately. This function can be used to avoid this.
|
||||||
*/
|
*/
|
||||||
void clearExitFlag();
|
void clearExitFlag() @safe nothrow;
|
||||||
|
|
||||||
/** Executes a callback in the thread owning the driver.
|
/** Executes a callback in the thread owning the driver.
|
||||||
*/
|
*/
|
||||||
void runInOwnerThread(ThreadCallback2 fun, intptr_t param1, intptr_t param2) shared;
|
void runInOwnerThread(ThreadCallbackGen fun, ref ThreadCallbackGenParams params) shared @safe nothrow;
|
||||||
/// ditto
|
/// ditto
|
||||||
final void runInOwnerThread(ThreadCallback1 fun, intptr_t param1)
|
final void runInOwnerThread(ARGS...)(void function(ARGS) @safe nothrow fun, ARGS args) shared
|
||||||
shared {
|
if (ARGS.length != 1 || !is(ARGS[0] == ThreadCallbackGenParams))
|
||||||
runInOwnerThread((p1, p2) {
|
{
|
||||||
auto f = () @trusted { return cast(ThreadCallback1)p2; } ();
|
alias F = void function(ARGS) @safe nothrow;
|
||||||
f(p1);
|
alias T = Tuple!ARGS;
|
||||||
}, param1, cast(intptr_t)fun);
|
static assert(F.sizeof + T.sizeof <= ThreadCallbackGenParams.length,
|
||||||
}
|
"Parameters take up too much space.");
|
||||||
/// ditto
|
|
||||||
final void runInOwnerThread(ThreadCallback0 fun)
|
ThreadCallbackGenParams params;
|
||||||
shared {
|
() @trusted { (cast(F[])params[0 .. F.sizeof])[0] = fun; } ();
|
||||||
runInOwnerThread((p1, p2) {
|
(cast(T[])params[F.sizeof .. F.sizeof + T.sizeof])[0] = T(args);
|
||||||
auto f = () @trusted { return cast(ThreadCallback0)p2; } ();
|
runInOwnerThread((ref ThreadCallbackGenParams p) {
|
||||||
f();
|
auto f = () @trusted { return cast(F[])p[0 .. F.sizeof]; } ()[0];
|
||||||
}, 0, cast(intptr_t)fun);
|
auto pt = () @trusted { return cast(T[])p[F.sizeof .. F.sizeof + T.sizeof]; } ();
|
||||||
|
f(pt[0].expand);
|
||||||
|
}, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Low-level user data access. Use `getUserData` instead.
|
/// Low-level user data access. Use `getUserData` instead.
|
||||||
|
@ -141,7 +142,7 @@ interface EventDriverCore {
|
||||||
*/
|
*/
|
||||||
deprecated("Use `EventDriverSockets.userData` instead.")
|
deprecated("Use `EventDriverSockets.userData` instead.")
|
||||||
@property final ref T userData(T, FD)(FD descriptor)
|
@property final ref T userData(T, FD)(FD descriptor)
|
||||||
@trusted {
|
@trusted nothrow {
|
||||||
import std.conv : emplace;
|
import std.conv : emplace;
|
||||||
static void init(void* ptr) { emplace(cast(T*)ptr); }
|
static void init(void* ptr) { emplace(cast(T*)ptr); }
|
||||||
static void destr(void* ptr) { destroy(*cast(T*)ptr); }
|
static void destr(void* ptr) { destroy(*cast(T*)ptr); }
|
||||||
|
@ -460,12 +461,11 @@ interface EventDriverFiles {
|
||||||
|
|
||||||
/** Disallows any reads/writes and removes any exclusive locks.
|
/** Disallows any reads/writes and removes any exclusive locks.
|
||||||
|
|
||||||
Note that this function may not actually close the file handle. The
|
Note that the file handle may become invalid at any point after the
|
||||||
handle is only guaranteed to be closed one the reference count drops
|
call to `close`, regardless of its current reference count. Any
|
||||||
to zero. However, the remaining effects of calling this function will
|
operations on the handle will not have an effect.
|
||||||
be similar to actually closing the file.
|
|
||||||
*/
|
*/
|
||||||
void close(FileFD file);
|
void close(FileFD file, FileCloseCallback on_closed);
|
||||||
|
|
||||||
ulong getSize(FileFD file);
|
ulong getSize(FileFD file);
|
||||||
|
|
||||||
|
@ -882,8 +882,12 @@ interface EventDriverPipes {
|
||||||
void waitForData(PipeFD pipe, PipeIOCallback on_data_available);
|
void waitForData(PipeFD pipe, PipeIOCallback on_data_available);
|
||||||
|
|
||||||
/** Immediately close the pipe. Future read or write operations may fail.
|
/** Immediately close the pipe. Future read or write operations may fail.
|
||||||
|
|
||||||
|
Note that the file handle may become invalid at any point after the
|
||||||
|
call to `close`, regardless of its current reference count. Any
|
||||||
|
operations on the handle will not have an effect.
|
||||||
*/
|
*/
|
||||||
void close(PipeFD pipe);
|
void close(PipeFD file, PipeCloseCallback on_closed);
|
||||||
|
|
||||||
/** Determines whether the given pipe handle is valid.
|
/** Determines whether the given pipe handle is valid.
|
||||||
|
|
||||||
|
@ -954,6 +958,7 @@ final class RefAddress : Address {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@safe: /*@nogc:*/ nothrow:
|
||||||
|
|
||||||
alias ConnectCallback = void delegate(StreamSocketFD, ConnectStatus);
|
alias ConnectCallback = void delegate(StreamSocketFD, ConnectStatus);
|
||||||
alias AcceptCallback = void delegate(StreamListenSocketFD, StreamSocketFD, scope RefAddress remote_address);
|
alias AcceptCallback = void delegate(StreamListenSocketFD, StreamSocketFD, scope RefAddress remote_address);
|
||||||
|
@ -961,7 +966,9 @@ alias IOCallback = void delegate(StreamSocketFD, IOStatus, size_t);
|
||||||
alias DatagramIOCallback = void delegate(DatagramSocketFD, IOStatus, size_t, scope RefAddress);
|
alias DatagramIOCallback = void delegate(DatagramSocketFD, IOStatus, size_t, scope RefAddress);
|
||||||
alias DNSLookupCallback = void delegate(DNSLookupID, DNSStatus, scope RefAddress[]);
|
alias DNSLookupCallback = void delegate(DNSLookupID, DNSStatus, scope RefAddress[]);
|
||||||
alias FileIOCallback = void delegate(FileFD, IOStatus, size_t);
|
alias FileIOCallback = void delegate(FileFD, IOStatus, size_t);
|
||||||
|
alias FileCloseCallback = void delegate(FileFD, CloseStatus);
|
||||||
alias PipeIOCallback = void delegate(PipeFD, IOStatus, size_t);
|
alias PipeIOCallback = void delegate(PipeFD, IOStatus, size_t);
|
||||||
|
alias PipeCloseCallback = void delegate(PipeFD, CloseStatus);
|
||||||
alias EventCallback = void delegate(EventID);
|
alias EventCallback = void delegate(EventID);
|
||||||
alias SignalCallback = void delegate(SignalListenID, SignalStatus, int);
|
alias SignalCallback = void delegate(SignalListenID, SignalStatus, int);
|
||||||
alias TimerCallback = void delegate(TimerID);
|
alias TimerCallback = void delegate(TimerID);
|
||||||
|
@ -984,6 +991,12 @@ enum ExitReason {
|
||||||
exited
|
exited
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CloseStatus {
|
||||||
|
ok,
|
||||||
|
ioError,
|
||||||
|
invalidHandle,
|
||||||
|
}
|
||||||
|
|
||||||
enum ConnectStatus {
|
enum ConnectStatus {
|
||||||
connected,
|
connected,
|
||||||
refused,
|
refused,
|
||||||
|
@ -1150,10 +1163,9 @@ mixin template Handle(string NAME, T, T invalid_value = T.init) {
|
||||||
alias value this;
|
alias value this;
|
||||||
}
|
}
|
||||||
|
|
||||||
alias ThreadCallback0 = void function() @safe nothrow;
|
alias ThreadCallbackGenParams = ubyte[8 * intptr_t.sizeof];
|
||||||
alias ThreadCallback1 = void function(intptr_t param1) @safe nothrow;
|
alias ThreadCallbackGen = void function(ref ThreadCallbackGenParams param3) @safe nothrow;
|
||||||
alias ThreadCallback2 = void function(intptr_t param1, intptr_t param2) @safe nothrow;
|
deprecated alias ThreadCallback = void function(intptr_t param1) @safe nothrow;
|
||||||
alias ThreadCallback = ThreadCallback1;
|
|
||||||
|
|
||||||
struct FD { mixin Handle!("fd", size_t, size_t.max); }
|
struct FD { mixin Handle!("fd", size_t, size_t.max); }
|
||||||
struct SocketFD { mixin Handle!("socket", FD); }
|
struct SocketFD { mixin Handle!("socket", FD); }
|
||||||
|
|
|
@ -160,7 +160,7 @@ final class PosixEventDriverCore(Loop : PosixEventLoop, Timers : EventDriverTime
|
||||||
|
|
||||||
protected alias ExtraEventsCallback = bool delegate(long);
|
protected alias ExtraEventsCallback = bool delegate(long);
|
||||||
|
|
||||||
private alias ThreadCallbackEntry = Tuple!(ThreadCallback2, intptr_t, intptr_t);
|
private alias ThreadCallbackEntry = Tuple!(ThreadCallbackGen, ThreadCallbackGenParams);
|
||||||
|
|
||||||
private {
|
private {
|
||||||
Loop m_loop;
|
Loop m_loop;
|
||||||
|
@ -262,7 +262,8 @@ final class PosixEventDriverCore(Loop : PosixEventLoop, Timers : EventDriverTime
|
||||||
m_exit = false;
|
m_exit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final override void runInOwnerThread(ThreadCallback2 del, intptr_t param1, intptr_t param2)
|
final override void runInOwnerThread(ThreadCallbackGen del,
|
||||||
|
ref ThreadCallbackGenParams params)
|
||||||
shared {
|
shared {
|
||||||
auto m = atomicLoad(m_threadCallbackMutex);
|
auto m = atomicLoad(m_threadCallbackMutex);
|
||||||
auto evt = atomicLoad(m_wakeupEvent);
|
auto evt = atomicLoad(m_wakeupEvent);
|
||||||
|
@ -275,7 +276,7 @@ final class PosixEventDriverCore(Loop : PosixEventLoop, Timers : EventDriverTime
|
||||||
try {
|
try {
|
||||||
synchronized (m)
|
synchronized (m)
|
||||||
() @trusted { return (cast()this).m_threadCallbacks; } ()
|
() @trusted { return (cast()this).m_threadCallbacks; } ()
|
||||||
.put(ThreadCallbackEntry(del, param1, param2));
|
.put(ThreadCallbackEntry(del, params));
|
||||||
} catch (Exception e) assert(false, e.msg);
|
} catch (Exception e) assert(false, e.msg);
|
||||||
|
|
||||||
m_events.trigger(m_wakeupEvent, false);
|
m_events.trigger(m_wakeupEvent, false);
|
||||||
|
@ -311,7 +312,7 @@ final class PosixEventDriverCore(Loop : PosixEventLoop, Timers : EventDriverTime
|
||||||
del = m_threadCallbacks.consumeOne;
|
del = m_threadCallbacks.consumeOne;
|
||||||
}
|
}
|
||||||
} catch (Exception e) assert(false, e.msg);
|
} catch (Exception e) assert(false, e.msg);
|
||||||
del[0](del[1], del[2]);
|
del[0](del[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import std.algorithm : min, max;
|
||||||
|
|
||||||
final class PosixEventDriverPipes(Loop : PosixEventLoop) : EventDriverPipes {
|
final class PosixEventDriverPipes(Loop : PosixEventLoop) : EventDriverPipes {
|
||||||
@safe: /*@nogc:*/ nothrow:
|
@safe: /*@nogc:*/ nothrow:
|
||||||
import core.stdc.errno : errno, EAGAIN;
|
import core.stdc.errno : errno, EAGAIN, EINTR;
|
||||||
import core.sys.posix.unistd : close, read, write;
|
import core.sys.posix.unistd : close, read, write;
|
||||||
import core.sys.posix.fcntl;
|
import core.sys.posix.fcntl;
|
||||||
import core.sys.posix.poll;
|
import core.sys.posix.poll;
|
||||||
|
@ -304,12 +304,21 @@ final class PosixEventDriverPipes(Loop : PosixEventLoop) : EventDriverPipes {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final override void close(PipeFD pipe)
|
final override void close(PipeFD pipe, PipeCloseCallback on_closed)
|
||||||
{
|
{
|
||||||
if (!isValid(pipe)) return;
|
if (!isValid(pipe)) {
|
||||||
|
on_closed(pipe, CloseStatus.invalidHandle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Maybe actually close here instead of waiting for releaseRef?
|
int res;
|
||||||
close(cast(int)pipe);
|
do res = close(cast(int)pipe);
|
||||||
|
while (res != 0 && errno == EINTR);
|
||||||
|
m_loop.unregisterFD(pipe, EventMask.read|EventMask.write|EventMask.status);
|
||||||
|
m_loop.clearFD!PipeSlot(pipe);
|
||||||
|
|
||||||
|
if (on_closed)
|
||||||
|
on_closed(pipe, res == 0 ? CloseStatus.ok : CloseStatus.ioError);
|
||||||
}
|
}
|
||||||
|
|
||||||
override bool isValid(PipeFD handle)
|
override bool isValid(PipeFD handle)
|
||||||
|
@ -337,10 +346,7 @@ final class PosixEventDriverPipes(Loop : PosixEventLoop) : EventDriverPipes {
|
||||||
nogc_assert(slot.common.refCount > 0, "Releasing reference to unreferenced pipe FD.");
|
nogc_assert(slot.common.refCount > 0, "Releasing reference to unreferenced pipe FD.");
|
||||||
|
|
||||||
if (--slot.common.refCount == 0) {
|
if (--slot.common.refCount == 0) {
|
||||||
m_loop.unregisterFD(pipe, EventMask.read|EventMask.write|EventMask.status);
|
close(pipe, null);
|
||||||
m_loop.clearFD!PipeSlot(pipe);
|
|
||||||
|
|
||||||
close(cast(int)pipe);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -386,8 +392,13 @@ final class DummyEventDriverPipes(Loop : PosixEventLoop) : EventDriverPipes {
|
||||||
assert(false, "TODO!");
|
assert(false, "TODO!");
|
||||||
}
|
}
|
||||||
|
|
||||||
override void close(PipeFD pipe)
|
override void close(PipeFD pipe, PipeCloseCallback on_closed)
|
||||||
{
|
{
|
||||||
|
if (!isValid(pipe)) {
|
||||||
|
on_closed(pipe, CloseStatus.invalidHandle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert(false, "TODO!");
|
assert(false, "TODO!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -206,7 +206,7 @@ final class PosixEventDriverProcesses(Loop : PosixEventLoop) : EventDriverProces
|
||||||
m_driver.core.runInOwnerThread(&onLocalProcessExit, system_pid);
|
m_driver.core.runInOwnerThread(&onLocalProcessExit, system_pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void onLocalProcessExit(intptr_t system_pid)
|
private static void onLocalProcessExit(int system_pid)
|
||||||
{
|
{
|
||||||
int exitCode;
|
int exitCode;
|
||||||
ProcessWaitCallback[] callbacks;
|
ProcessWaitCallback[] callbacks;
|
||||||
|
@ -214,12 +214,12 @@ final class PosixEventDriverProcesses(Loop : PosixEventLoop) : EventDriverProces
|
||||||
ProcessID pid;
|
ProcessID pid;
|
||||||
|
|
||||||
PosixEventDriverProcesses driver;
|
PosixEventDriverProcesses driver;
|
||||||
lockedProcessInfoPlain(cast(int)system_pid, (info) {
|
lockedProcessInfoPlain(system_pid, (info) {
|
||||||
assert(info !is null);
|
assert(info !is null);
|
||||||
|
|
||||||
exitCode = info.exitCode;
|
exitCode = info.exitCode;
|
||||||
callbacks = info.callbacks;
|
callbacks = info.callbacks;
|
||||||
pid = ProcessID(cast(int)system_pid, info.validationCounter);
|
pid = ProcessID(system_pid, info.validationCounter);
|
||||||
info.callbacks = null;
|
info.callbacks = null;
|
||||||
|
|
||||||
driver = info.driver;
|
driver = info.driver;
|
||||||
|
|
|
@ -102,7 +102,6 @@ final class ThreadedFileEventDriver(Events : EventDriverEvents) : EventDriverFil
|
||||||
static struct FileInfo {
|
static struct FileInfo {
|
||||||
IOInfo read;
|
IOInfo read;
|
||||||
IOInfo write;
|
IOInfo write;
|
||||||
bool open = true;
|
|
||||||
|
|
||||||
uint validationCounter;
|
uint validationCounter;
|
||||||
|
|
||||||
|
@ -177,15 +176,22 @@ final class ThreadedFileEventDriver(Events : EventDriverEvents) : EventDriverFil
|
||||||
return FileFD(system_file_handle, vc + 1);
|
return FileFD(system_file_handle, vc + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void close(FileFD file)
|
void close(FileFD file, FileCloseCallback on_closed)
|
||||||
{
|
{
|
||||||
if (!isValid(file)) return;
|
if (!isValid(file)) {
|
||||||
|
on_closed(file, CloseStatus.invalidHandle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: The file descriptor itself must stay open until the reference
|
// TODO: close may block and should be executed in a worker thread
|
||||||
// count drops to zero, or this would result in dangling handles.
|
int res;
|
||||||
// In case of an exclusive file lock, the lock should be lifted
|
do res = .close(cast(int)file.value);
|
||||||
// here.
|
while (res != 0 && errno == EINTR);
|
||||||
m_files[file].open = false;
|
|
||||||
|
m_files[file.value] = FileInfo.init;
|
||||||
|
|
||||||
|
if (on_closed)
|
||||||
|
on_closed(file, res == 0 ? CloseStatus.ok : CloseStatus.ioError);
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong getSize(FileFD file)
|
ulong getSize(FileFD file)
|
||||||
|
@ -230,11 +236,6 @@ final class ThreadedFileEventDriver(Events : EventDriverEvents) : EventDriverFil
|
||||||
//assert(this.writable);
|
//assert(this.writable);
|
||||||
auto f = () @trusted { return &m_files[file]; } ();
|
auto f = () @trusted { return &m_files[file]; } ();
|
||||||
|
|
||||||
if (!f.open) {
|
|
||||||
on_write_finish(file, IOStatus.disconnected, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!safeCAS(f.write.status, ThreadedFileStatus.idle, ThreadedFileStatus.initiated))
|
if (!safeCAS(f.write.status, ThreadedFileStatus.idle, ThreadedFileStatus.initiated))
|
||||||
assert(false, "Concurrent file writes are not allowed.");
|
assert(false, "Concurrent file writes are not allowed.");
|
||||||
assert(f.write.callback is null, "Concurrent file writes are not allowed.");
|
assert(f.write.callback is null, "Concurrent file writes are not allowed.");
|
||||||
|
@ -276,11 +277,6 @@ final class ThreadedFileEventDriver(Events : EventDriverEvents) : EventDriverFil
|
||||||
|
|
||||||
auto f = () @trusted { return &m_files[file]; } ();
|
auto f = () @trusted { return &m_files[file]; } ();
|
||||||
|
|
||||||
if (!f.open) {
|
|
||||||
on_read_finish(file, IOStatus.disconnected, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!safeCAS(f.read.status, ThreadedFileStatus.idle, ThreadedFileStatus.initiated))
|
if (!safeCAS(f.read.status, ThreadedFileStatus.idle, ThreadedFileStatus.initiated))
|
||||||
assert(false, "Concurrent file reads are not allowed.");
|
assert(false, "Concurrent file reads are not allowed.");
|
||||||
assert(f.read.callback is null, "Concurrent file reads are not allowed.");
|
assert(f.read.callback is null, "Concurrent file reads are not allowed.");
|
||||||
|
@ -332,8 +328,7 @@ final class ThreadedFileEventDriver(Events : EventDriverEvents) : EventDriverFil
|
||||||
|
|
||||||
auto f = () @trusted { return &m_files[descriptor]; } ();
|
auto f = () @trusted { return &m_files[descriptor]; } ();
|
||||||
if (!--f.refCount) {
|
if (!--f.refCount) {
|
||||||
.close(cast(int)descriptor);
|
close(descriptor, null);
|
||||||
*f = FileInfo.init;
|
|
||||||
assert(!m_activeReads.contains(descriptor.value));
|
assert(!m_activeReads.contains(descriptor.value));
|
||||||
assert(!m_activeWrites.contains(descriptor.value));
|
assert(!m_activeWrites.contains(descriptor.value));
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -16,7 +16,7 @@ import std.typecons : Tuple, tuple;
|
||||||
|
|
||||||
final class WinAPIEventDriverCore : EventDriverCore {
|
final class WinAPIEventDriverCore : EventDriverCore {
|
||||||
@safe: /*@nogc:*/ nothrow:
|
@safe: /*@nogc:*/ nothrow:
|
||||||
private alias ThreadCallbackEntry = Tuple!(ThreadCallback2, intptr_t, intptr_t);
|
private alias ThreadCallbackEntry = Tuple!(ThreadCallbackGen, ThreadCallbackGenParams);
|
||||||
|
|
||||||
private {
|
private {
|
||||||
bool m_exit;
|
bool m_exit;
|
||||||
|
@ -141,7 +141,8 @@ final class WinAPIEventDriverCore : EventDriverCore {
|
||||||
m_exit = false;
|
m_exit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
override void runInOwnerThread(ThreadCallback2 del, intptr_t param1, intptr_t param2)
|
override void runInOwnerThread(ThreadCallbackGen del,
|
||||||
|
ref ThreadCallbackGenParams params)
|
||||||
shared {
|
shared {
|
||||||
import core.atomic : atomicLoad;
|
import core.atomic : atomicLoad;
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@ final class WinAPIEventDriverCore : EventDriverCore {
|
||||||
try {
|
try {
|
||||||
synchronized (m)
|
synchronized (m)
|
||||||
() @trusted { return (cast()this).m_threadCallbacks; } ()
|
() @trusted { return (cast()this).m_threadCallbacks; } ()
|
||||||
.put(ThreadCallbackEntry(del, param1, param2));
|
.put(ThreadCallbackEntry(del, params));
|
||||||
} catch (Exception e) assert(false, e.msg);
|
} catch (Exception e) assert(false, e.msg);
|
||||||
|
|
||||||
() @trusted { PostThreadMessageW(m_tid, WM_APP, 0, 0); } ();
|
() @trusted { PostThreadMessageW(m_tid, WM_APP, 0, 0); } ();
|
||||||
|
@ -279,8 +280,6 @@ final class WinAPIEventDriverCore : EventDriverCore {
|
||||||
|
|
||||||
private void executeThreadCallbacks()
|
private void executeThreadCallbacks()
|
||||||
{
|
{
|
||||||
import std.stdint : intptr_t;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ThreadCallbackEntry del;
|
ThreadCallbackEntry del;
|
||||||
try {
|
try {
|
||||||
|
@ -289,7 +288,7 @@ final class WinAPIEventDriverCore : EventDriverCore {
|
||||||
del = m_threadCallbacks.consumeOne;
|
del = m_threadCallbacks.consumeOne;
|
||||||
}
|
}
|
||||||
} catch (Exception e) assert(false, e.msg);
|
} catch (Exception e) assert(false, e.msg);
|
||||||
del[0](del[1], del[2]);
|
del[0](del[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,6 +359,7 @@ package struct FileSlot {
|
||||||
}
|
}
|
||||||
Direction!false read;
|
Direction!false read;
|
||||||
Direction!true write;
|
Direction!true write;
|
||||||
|
FileCloseCallback closeCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
package struct WatcherSlot {
|
package struct WatcherSlot {
|
||||||
|
|
|
@ -4,6 +4,7 @@ version (Windows):
|
||||||
|
|
||||||
import eventcore.driver;
|
import eventcore.driver;
|
||||||
import eventcore.drivers.winapi.core;
|
import eventcore.drivers.winapi.core;
|
||||||
|
import eventcore.internal.ioworker;
|
||||||
import eventcore.internal.win32;
|
import eventcore.internal.win32;
|
||||||
|
|
||||||
private extern(Windows) @trusted nothrow @nogc {
|
private extern(Windows) @trusted nothrow @nogc {
|
||||||
|
@ -14,6 +15,7 @@ final class WinAPIEventDriverFiles : EventDriverFiles {
|
||||||
@safe /*@nogc*/ nothrow:
|
@safe /*@nogc*/ nothrow:
|
||||||
private {
|
private {
|
||||||
WinAPIEventDriverCore m_core;
|
WinAPIEventDriverCore m_core;
|
||||||
|
IOWorkerPool m_workerPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
this(WinAPIEventDriverCore core)
|
this(WinAPIEventDriverCore core)
|
||||||
|
@ -71,15 +73,41 @@ final class WinAPIEventDriverFiles : EventDriverFiles {
|
||||||
return FileFD(cast(size_t)handle, m_core.m_handles[handle].validationCounter);
|
return FileFD(cast(size_t)handle, m_core.m_handles[handle].validationCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void close(FileFD file)
|
override void close(FileFD file, FileCloseCallback on_closed)
|
||||||
{
|
{
|
||||||
if (!isValid(file)) return;
|
static void doCloseCleanup(CloseParams p)
|
||||||
|
{
|
||||||
|
p.core.removeWaiter();
|
||||||
|
if (p.callback) p.callback(p.file, p.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void doClose(FileFD file, FileCloseCallback on_closed,
|
||||||
|
shared(WinAPIEventDriverCore) core)
|
||||||
|
{
|
||||||
|
CloseParams p;
|
||||||
|
CloseStatus st;
|
||||||
|
p.file = file;
|
||||||
|
p.callback = on_closed;
|
||||||
|
p.core = () @trusted { return cast(WinAPIEventDriverCore)core; } ();
|
||||||
|
if (CloseHandle(idToHandle(file))) p.status = CloseStatus.ok;
|
||||||
|
else p.status = CloseStatus.ioError;
|
||||||
|
|
||||||
|
() @trusted { core.runInOwnerThread(&doCloseCleanup, p); } ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid(file)) {
|
||||||
|
on_closed(file, CloseStatus.invalidHandle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto h = idToHandle(file);
|
auto h = idToHandle(file);
|
||||||
auto slot = () @trusted { return &m_core.m_handles[h]; } ();
|
auto slot = () @trusted { return &m_core.m_handles[h]; } ();
|
||||||
if (slot.validationCounter != file.validationCounter) return;
|
|
||||||
if (slot.file.read.overlapped.hEvent != INVALID_HANDLE_VALUE)
|
if (slot.file.read.overlapped.hEvent != INVALID_HANDLE_VALUE)
|
||||||
slot.file.read.overlapped.hEvent = slot.file.write.overlapped.hEvent = INVALID_HANDLE_VALUE;
|
slot.file.read.overlapped.hEvent = slot.file.write.overlapped.hEvent = INVALID_HANDLE_VALUE;
|
||||||
|
m_core.addWaiter();
|
||||||
|
m_core.discardEvents(&slot.file.read.overlapped, &slot.file.write.overlapped);
|
||||||
|
m_core.freeSlot(h);
|
||||||
|
workerPool.run!doClose(file, on_closed, () @trusted { return cast(shared)m_core; } ());
|
||||||
}
|
}
|
||||||
|
|
||||||
override ulong getSize(FileFD file)
|
override ulong getSize(FileFD file)
|
||||||
|
@ -213,11 +241,7 @@ final class WinAPIEventDriverFiles : EventDriverFiles {
|
||||||
|
|
||||||
auto h = idToHandle(descriptor);
|
auto h = idToHandle(descriptor);
|
||||||
auto slot = &m_core.m_handles[h];
|
auto slot = &m_core.m_handles[h];
|
||||||
return slot.releaseRef({
|
return slot.releaseRef({ close(descriptor, null); });
|
||||||
CloseHandle(h);
|
|
||||||
m_core.discardEvents(&slot.file.read.overlapped, &slot.file.write.overlapped);
|
|
||||||
m_core.freeSlot(h);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void* rawUserData(FileFD descriptor, size_t size, DataInitializer initialize, DataInitializer destroy)
|
protected override void* rawUserData(FileFD descriptor, size_t size, DataInitializer initialize, DataInitializer destroy)
|
||||||
|
@ -291,8 +315,22 @@ final class WinAPIEventDriverFiles : EventDriverFiles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @property ref IOWorkerPool workerPool()
|
||||||
|
{
|
||||||
|
if (!m_workerPool)
|
||||||
|
m_workerPool = acquireIOWorkerPool();
|
||||||
|
return m_workerPool;
|
||||||
|
}
|
||||||
|
|
||||||
private static HANDLE idToHandle(FileFD id)
|
private static HANDLE idToHandle(FileFD id)
|
||||||
@trusted @nogc {
|
@trusted @nogc {
|
||||||
return cast(HANDLE)cast(size_t)id;
|
return cast(HANDLE)cast(size_t)id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static struct CloseParams {
|
||||||
|
FileFD file;
|
||||||
|
FileCloseCallback callback;
|
||||||
|
CloseStatus status;
|
||||||
|
WinAPIEventDriverCore core;
|
||||||
|
}
|
||||||
|
|
|
@ -53,9 +53,13 @@ final class WinAPIEventDriverPipes : EventDriverPipes {
|
||||||
assert(false, "TODO!");
|
assert(false, "TODO!");
|
||||||
}
|
}
|
||||||
|
|
||||||
override void close(PipeFD pipe)
|
override void close(PipeFD pipe, PipeCloseCallback on_closed)
|
||||||
{
|
{
|
||||||
if (!isValid(pipe)) return;
|
if (!isValid(pipe)) {
|
||||||
|
if (on_closed)
|
||||||
|
on_closed(pipe, CloseStatus.invalidHandle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert(false, "TODO!");
|
assert(false, "TODO!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ module eventcore.internal.ioworker;
|
||||||
|
|
||||||
import eventcore.internal.utils;
|
import eventcore.internal.utils;
|
||||||
|
|
||||||
import std.parallelism : TaskPool;
|
import std.parallelism : TaskPool, Task, task;
|
||||||
|
|
||||||
|
|
||||||
IOWorkerPool acquireIOWorkerPool()
|
IOWorkerPool acquireIOWorkerPool()
|
||||||
|
@ -28,6 +28,14 @@ struct IOWorkerPool {
|
||||||
@property TaskPool pool() { return m_pool; }
|
@property TaskPool pool() { return m_pool; }
|
||||||
|
|
||||||
alias pool this;
|
alias pool this;
|
||||||
|
|
||||||
|
auto run(alias fun, ARGS...)(ARGS args)
|
||||||
|
{
|
||||||
|
auto t = task!(fun, ARGS)(args);
|
||||||
|
try m_pool.put(t);
|
||||||
|
catch (Exception e) assert(false, e.msg);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maintains a single thread pool shared by all driver instances (threads)
|
// Maintains a single thread pool shared by all driver instances (threads)
|
||||||
|
|
|
@ -36,7 +36,8 @@ void main()
|
||||||
assert(status == IOStatus.ok);
|
assert(status == IOStatus.ok);
|
||||||
assert(nbytes == data.length - 5);
|
assert(nbytes == data.length - 5);
|
||||||
assert(dst == data);
|
assert(dst == data);
|
||||||
eventDriver.files.close(f);
|
eventDriver.files.close(f, (f, s) {
|
||||||
|
assert(s == CloseStatus.ok);
|
||||||
() @trusted {
|
() @trusted {
|
||||||
scope (failure) assert(false);
|
scope (failure) assert(false);
|
||||||
remove("test.txt");
|
remove("test.txt");
|
||||||
|
@ -47,6 +48,7 @@ void main()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
ExitReason er;
|
ExitReason er;
|
||||||
do er = eventDriver.core.processEvents(Duration.max);
|
do er = eventDriver.core.processEvents(Duration.max);
|
||||||
|
|
|
@ -31,7 +31,7 @@ void main()
|
||||||
|
|
||||||
void threadFunc(shared(NativeEventDriver) drv)
|
void threadFunc(shared(NativeEventDriver) drv)
|
||||||
{
|
{
|
||||||
drv.core.runInOwnerThread((id) {
|
drv.core.runInOwnerThread((int id) {
|
||||||
s_id = id;
|
s_id = id;
|
||||||
}, 42);
|
}, 42);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue