vibe-d / eventcore

High performance proactor event loop abstraction library
MIT License
60 stars 42 forks source link

TaggedUnion assert failure when waiting an event from a different thread #238

Open yellowsink opened 4 months ago

yellowsink commented 4 months ago

When attempting to wait for an event created on the main thread from a second thread (as far as I can tell from the documentation, that is allowed), the wait() call (not even processevents!) causes an assertion failure in taggedunion.

I've attached a minimal reproduction, but for reference, I encountered this within a more complex project with this demo code: https://github.com/yellowsink/rockhopper/blob/ed05a03/source/app.d

Minimal reproduction code:

import eventcore.core : eventDriver;
import core.thread.osthread : Thread;
import std.stdio : writeln;

void main()
{
    auto drv1 = eventDriver;
    //shared drv1shared = cast(shared) eventDriver; // only necessary to trigger from thread 2, irrelevant

    // making this `shared` has no effect.
    auto ev1_to_2 = drv1.events.create();

    new Thread({
        auto drv2 = eventDriver;

        try {
            drv2.events.wait(ev1_to_2, (_e) nothrow {});
        }
        catch (Exception t)
        {
            writeln(t);
        }
    }).start();//.join(); has no effect
}

library versions:

{
    "fileVersion": 1,
    "versions": {
        "eventcore": "0.9.30",
        "libasync": "0.8.6",
        "memutils": "1.0.10",
        "taggedalgebraic": "0.11.23"
    }
}

exception message (DMD, LDC gives a more compact stack trace):

core.exception.AssertError@../../.dub/packages/taggedalgebraic/0.11.23/taggedalgebraic/source/taggedalgebraic/taggedunion.d(309): Attempting to get type EventSlot from a TaggedUnion with type typeof(null)
----------------
??:? _d_assert_msg [0x5be29787a22c]
../../.dub/packages/taggedalgebraic/0.11.23/taggedalgebraic/source/taggedalgebraic/taggedunion.d:309 inout pure nothrow ref @property @nogc @safe inout(eventcore.drivers.posix.events.EventSlot) taggedalgebraic.taggedunion.TaggedUnion!(eventcore.internal.utils.AlgebraicChoppedVector!(eventcore.drivers.posix.driver.FDSlot, eventcore.drivers.posix.sockets.StreamSocketSlot, eventcore.drivers.posix.sockets.StreamListenSocketSlot, eventcore.drivers.posix.sockets.DgramSocketSlot, eventcore.drivers.posix.dns.DNSSlot, eventcore.drivers.posix.watchers.WatcherSlot, eventcore.drivers.posix.events.EventSlot, eventcore.drivers.posix.signals.SignalSlot, eventcore.drivers.posix.pipes.PipeSlot).AlgebraicChoppedVector.U).TaggedUnion.value!(eventcore.drivers.posix.events.EventSlot).value() [0x5be297845a1b]
../../.dub/packages/taggedalgebraic/0.11.23/taggedalgebraic/source/taggedalgebraic/taggedalgebraic.d:767 pure nothrow ref @nogc @safe inout(eventcore.drivers.posix.events.EventSlot) taggedalgebraic.taggedalgebraic.get!(eventcore.drivers.posix.events.EventSlot, eventcore.internal.utils.AlgebraicChoppedVector!(eventcore.drivers.posix.driver.FDSlot, eventcore.drivers.posix.sockets.StreamSocketSlot, eventcore.drivers.posix.sockets.StreamListenSocketSlot, eventcore.drivers.posix.sockets.DgramSocketSlot, eventcore.drivers.posix.dns.DNSSlot, eventcore.drivers.posix.watchers.WatcherSlot, eventcore.drivers.posix.events.EventSlot, eventcore.drivers.posix.signals.SignalSlot, eventcore.drivers.posix.pipes.PipeSlot).AlgebraicChoppedVector.U).get(ref inout(taggedalgebraic.taggedalgebraic.TaggedAlgebraic!(eventcore.internal.utils.AlgebraicChoppedVector!(eventcore.drivers.posix.driver.FDSlot, eventcore.drivers.posix.sockets.StreamSocketSlot, eventcore.drivers.posix.sockets.StreamListenSocketSlot, eventcore.drivers.posix.sockets.DgramSocketSlot, eventcore.drivers.posix.dns.DNSSlot, eventcore.drivers.posix.watchers.WatcherSlot, eventcore.drivers.posix.events.EventSlot, eventcore.drivers.posix.signals.SignalSlot, eventcore.drivers.posix.pipes.PipeSlot).AlgebraicChoppedVector.U).TaggedAlgebraic)) [0x5be2978459b4]
../../.dub/packages/eventcore/0.9.30/eventcore/source/eventcore/internal/utils.d-mixin-311:316 pure nothrow ref @property @nogc @safe eventcore.drivers.posix.events.EventSlot eventcore.internal.utils.AlgebraicChoppedVector!(eventcore.drivers.posix.driver.FDSlot, eventcore.drivers.posix.sockets.StreamSocketSlot, eventcore.drivers.posix.sockets.StreamListenSocketSlot, eventcore.drivers.posix.sockets.DgramSocketSlot, eventcore.drivers.posix.dns.DNSSlot, eventcore.drivers.posix.watchers.WatcherSlot, eventcore.drivers.posix.events.EventSlot, eventcore.drivers.posix.signals.SignalSlot, eventcore.drivers.posix.pipes.PipeSlot).AlgebraicChoppedVector.FullField.event() [0x5be2978384ab]
../../.dub/packages/eventcore/0.9.30/eventcore/source/eventcore/drivers/posix/events.d:251 nothrow @nogc @trusted eventcore.drivers.posix.events.EventSlot* eventcore.drivers.posix.events.PosixEventDriverEvents!(eventcore.drivers.posix.epoll.EpollEventLoop, eventcore.drivers.posix.sockets.PosixEventDriverSockets!(eventcore.drivers.posix.epoll.EpollEventLoop).PosixEventDriverSockets).PosixEventDriverEvents.getSlot(eventcore.driver.EventID).__lambda2() [0x5be29782adf2]
../../.dub/packages/eventcore/0.9.30/eventcore/source/eventcore/drivers/posix/events.d:251 nothrow @nogc @safe eventcore.drivers.posix.events.EventSlot* eventcore.drivers.posix.events.PosixEventDriverEvents!(eventcore.drivers.posix.epoll.EpollEventLoop, eventcore.drivers.posix.sockets.PosixEventDriverSockets!(eventcore.drivers.posix.epoll.EpollEventLoop).PosixEventDriverSockets).PosixEventDriverEvents.getSlot(eventcore.driver.EventID) [0x5be29782adb8]
../../.dub/packages/eventcore/0.9.30/eventcore/source/eventcore/drivers/posix/events.d:261 nothrow @nogc @safe bool eventcore.drivers.posix.events.PosixEventDriverEvents!(eventcore.drivers.posix.epoll.EpollEventLoop, eventcore.drivers.posix.sockets.PosixEventDriverSockets!(eventcore.drivers.posix.epoll.EpollEventLoop).PosixEventDriverSockets).PosixEventDriverEvents.isInternal(eventcore.driver.EventID) [0x5be29782ae54]
../../.dub/packages/eventcore/0.9.30/eventcore/source/eventcore/drivers/posix/events.d:155 nothrow @nogc @safe void eventcore.drivers.posix.events.PosixEventDriverEvents!(eventcore.drivers.posix.epoll.EpollEventLoop, eventcore.drivers.posix.sockets.PosixEventDriverSockets!(eventcore.drivers.posix.epoll.EpollEventLoop).PosixEventDriverSockets).PosixEventDriverEvents.wait(eventcore.driver.EventID, void delegate(eventcore.driver.EventID) nothrow @safe) [0x5be29782a92f]
source/app.d:26 nothrow void app.main().__lambda3() [0x5be2977fbe91]
??:? void core.thread.context.Callable.opCall() [0x5be29787f73c]
??:? thread_entryPoint [0x5be29787f21a]
??:? [0x786ce035f559]
??:? [0x786ce03dca3b]
yellowsink commented 4 months ago

If it is simply the case that threads may only wait for events created on their own thread, I think this should be documented more clearly, but that also would significantly increase the difficulty of syncing threads so I'm hoping this is just a bug that can be fixed!

s-ludwig commented 4 months ago

The problem here is that each event ID is only valid for the event driver instance that created it. So in order to wait from another thread, you have to pass the main thread's eventDriver as cast(shared)eventDriver to the other thread and then call the shared overload of wait there. I'll add some documentation for this.

yellowsink commented 4 months ago

ah! I was aware of shared trigger but not shared wait. doesn't this mean that you'd need to use a shared processevents too, though? I suppose the only way to do what I want is to make sure it's constructed on the second thread... should be doable

s-ludwig commented 4 months ago

Oh wait, you are right, I misremembered that. There is indeed no shared wait! So it actually needs to be turned around to work and the waiting thread needs to create the event. I'm not quite sure whether this is something that can be improved without introducing additional locking within the driver, which would certainly impair performance.

yellowsink commented 4 months ago

I can probably work around this in my wrapper struct I have - I can keep a map of thread ids to (eventid, shared(eventdriver)) and that should work, but I suppose if making this work would result in a general slowdown for usage of events in eventcore its most likely better placed as a hit taken in user code than there.

yellowsink commented 4 months ago

I can probably work around this in my wrapper struct I have

I've got this working - so I suppose it's really just a docs change that's needed to clarify the requirement to wait on the same thread as create