September 23, 2021

On 9/23/21 12:53 PM, eugene wrote:

>

On Thursday, 23 September 2021 at 15:53:37 UTC, Steven Schveighoffer wrote:

>

Technically, they should live past the end of main, because it's still possible to receive signals then.

No, as soon as an application get SIGTERM/SIGINT,
event queue is stopped and we do not need no
more notifications from OS (POLLIN/POLLOUT I mean).

Stopping event queue in this case is just
closing file descriptor obtained from epoll_create().
After this getting POLLIN from any fd (including signal fd) is
just impossible.

That's not what is triggering the segfault though. The segfault is triggered by the signal handler referencing the destroyed object.

So imagine the sequence:

  1. ctrl-c, signal handler triggers, shutting down the loop
  2. main exits
  3. GC finalizes all objects, including the Stopper and it's members
  4. ctrl-c happens again, but you didn't unregister the signal handler, so it's run again, referencing the now-deleted object.
  5. segfault

It's theoretically a very very small window.

-Steve

September 23, 2021

On Thursday, 23 September 2021 at 17:20:18 UTC, Steven Schveighoffer wrote:

>

So imagine the sequence:

With ease!

>
  1. ctrl-c, signal handler triggers, shutting down the loop

Just a note: there is no 'signal handler' in the program.
SIGINT/SIGTERM are blocked, notifications (POLLIN) are received via epoll_wait().

>
  1. main exits
  2. GC finalizes all objects, including the Stopper and it's members

Probably, a destructor for Signal class should be added, in which

  • close fd, obtained from signalfd()
  • unblock the signal (thus default signal handler is back again)
>
  1. ctrl-c happens again, but you didn't unregister the signal handler, so it's run again, referencing the now-deleted object.

At this point we have default signal handler

>
  1. segfault
    It's theoretically a very very small window.

But even without destructor, no segfault will happen,
because there is no signal handler

September 23, 2021

On Thursday, 23 September 2021 at 17:20:18 UTC, Steven Schveighoffer wrote:

>
  1. ctrl-c, signal handler triggers, shutting down the loop
  2. main exits
  3. GC finalizes all objects, including the Stopper and it's members

but both SIGINT and SIGTERM are still blocked,
they just will not reach the process.

September 23, 2021

On Thursday, 23 September 2021 at 17:49:43 UTC, eugene wrote:

>

On Thursday, 23 September 2021 at 17:20:18 UTC, Steven Schveighoffer wrote:

>
  1. ctrl-c, signal handler triggers, shutting down the loop
  2. main exits
  3. GC finalizes all objects, including the Stopper and it's members

but both SIGINT and SIGTERM are still blocked,
they just will not reach the process.

oops..

closing epoll fd should be moved from EventQueue dtor to stop() method,
then everything will be Ok.

September 23, 2021

On Thursday, 23 September 2021 at 17:53:00 UTC, eugene wrote:

>

On Thursday, 23 September 2021 at 17:49:43 UTC, eugene wrote:

>

On Thursday, 23 September 2021 at 17:20:18 UTC, Steven Schveighoffer wrote:

>
  1. ctrl-c, signal handler triggers, shutting down the loop
  2. main exits
  3. GC finalizes all objects, including the Stopper and it's members

but both SIGINT and SIGTERM are still blocked,
they just will not reach the process.

oops..

no oops, that's all right.

  • when creating Signal instance, corresponding signal becames blocked
final class Signal : EventSource {

    enum int sigInt = SIGINT;
    enum int sigTerm = SIGTERM;
    ulong number;

    this(int signo) {
        super('S');

        sigset_t sset;
        sigset_t old_sset;
        /* block the signal */
        sigemptyset(&sset);
        sigaddset(&sset, signo);
        sigprocmask(SIG_BLOCK, &sset, &old_sset);

        id = signalfd(-1, &sset, SFD_CLOEXEC);
  • upon receiving SIGINT stopperIdleS0() is called.
    now stop variable of EventQueue is false

  • next call to wait() method just return.
    (remember, signals are still blocked)

September 23, 2021

On Thursday, 23 September 2021 at 17:16:23 UTC, Steven Schveighoffer wrote:

>

On 9/23/21 12:58 PM, eugene wrote:

>

On Thursday, 23 September 2021 at 15:56:16 UTC, Steven Schveighoffer wrote:

>

See more details:

https://docs.microsoft.com/en-us/dotnet/api/system.gc.keepalive?view=net-5.0#remarks

"
This method references the obj parameter, making that object ineligible for garbage collection from the start of the routine to the point, in execution order, where this method is called. Code this method at the end, not the beginning, of the range of instructions where obj must be available.
"

Code this method at the end...

:)
it is the same as proposed by jfondren simple
writeln(stopper.sg0.number) in the end of main, right?

Same effect, but writeln actually executes code to write data to the console, whereas KeepAlive doesn't do anything.

void keepAlive(Object o) {
}

void main(string[] args) {

    import core.memory : GC;

    auto Main = new Main();
    Main.run();

    auto stopper = new Stopper();
    stopper.run();

    writeln(" === Hello, world! === ");
    auto md = new MessageDispatcher();
    md.loop();

    keepAlive(Main);
    keepAlive(stopper);

    writeln(" === Goodbye, world! === ");
}

works ok with dmd, stopper is not collected.

September 23, 2021

On 9/23/21 2:18 PM, eugene wrote:

>

On Thursday, 23 September 2021 at 17:16:23 UTC, Steven Schveighoffer wrote:

>

On 9/23/21 12:58 PM, eugene wrote:

>

On Thursday, 23 September 2021 at 15:56:16 UTC, Steven Schveighoffer wrote:

>

See more details:

https://docs.microsoft.com/en-us/dotnet/api/system.gc.keepalive?view=net-5.0#remarks

"
This method references the obj parameter, making that object ineligible for garbage collection from the start of the routine to the point, in execution order, where this method is called. Code this method at the end, not the beginning, of the range of instructions where obj must be available.
"

Code this method at the end...

:)
it is the same as proposed by jfondren simple
writeln(stopper.sg0.number) in the end of main, right?

Same effect, but writeln actually executes code to write data to the console, whereas KeepAlive doesn't do anything.

void keepAlive(Object o) {
}

void main(string[] args) {

     import core.memory : GC;

     auto Main = new Main();
     Main.run();

     auto stopper = new Stopper();
     stopper.run();

     writeln(" === Hello, world! === ");
     auto md = new MessageDispatcher();
     md.loop();

     keepAlive(Main);
     keepAlive(stopper);

     writeln(" === Goodbye, world! === ");
}

works ok with dmd, stopper is not collected.

With dmd -O -inline, there is a chance it will be collected. Inlining is key here.

-Steve

September 23, 2021

On Thursday, 23 September 2021 at 18:43:36 UTC, Steven Schveighoffer wrote:

>

With dmd -O -inline, there is a chance it will be collected. Inlining is key here.

never mind, GC.addRoot() looks more trustworthy, anyway :)

September 23, 2021

On 9/23/21 1:44 PM, eugene wrote:

>

On Thursday, 23 September 2021 at 17:20:18 UTC, Steven Schveighoffer wrote:

>

So imagine the sequence:

With ease!

>
  1. ctrl-c, signal handler triggers, shutting down the loop

Just a note: there is no 'signal handler' in the program.
SIGINT/SIGTERM are blocked, notifications (POLLIN) are received via epoll_wait().

Oh interesting! I didn't read the code closely enough.

> >
  1. main exits
  2. GC finalizes all objects, including the Stopper and it's members

Probably, a destructor for Signal class should be added, in which

  • close fd, obtained from signalfd()
  • unblock the signal (thus default signal handler is back again)

Yes, I would recommend that. Always good for a destructor to clean up any non-GC resources that haven't already been cleaned up. That's actually what class destructors are for.

> >
  1. ctrl-c happens again, but you didn't unregister the signal handler, so it's run again, referencing the now-deleted object.

At this point we have default signal handler

>
  1. segfault
    It's theoretically a very very small window.

But even without destructor, no segfault will happen,
because there is no signal handler

So it gets written to the file descriptor instead? And nobody is there reading it, so it's just closed along with the process?

I've not done signals this way, it seems pretty clever and less prone to asynchronous issues.

-Steve

September 23, 2021

On Thursday, 23 September 2021 at 18:53:25 UTC, Steven Schveighoffer wrote:

>

On 9/23/21 1:44 PM, eugene wrote:

>

Just a note: there is no 'signal handler' in the program.
SIGINT/SIGTERM are blocked, notifications (POLLIN) are received via epoll_wait().

Oh interesting! I didn't read the code closely enough.

"everything in Unix is a file" (c)

All event sources (sockets, timers, signal, file system events)
can be 'routed' through i/o multiplexing facilities, like
select/poll(posix)/epoll(linux)/queue(freebsd) etc.

> >

Probably, a destructor for Signal class should be added, in which

>

Yes, I would recommend that. Always good for a destructor to clean up any non-GC resources that haven't already been cleaned up. That's actually what class destructors are for.

No, destructors are not necessary, since after SIGINT/SIRTERM
program is about to terminate and all resources will be
released anyway.

In C I do same way - do not close fd, which live from
start to end, do not free() pointers and so on, no need.

>

So it gets written to the file descriptor instead?

When signal happens (or timer expires, or file is deleted)
process get EPOLLIN on corresponding file descriptor
via epoll_wait() and then process has to read some info
from these file descriptors.

>

And nobody is there reading it, so it's just closed along with the process?

Yes, as any other file descriptor.

>

I've not done signals this way, it seems pretty clever and less prone to asynchronous issues.

It's just great, thanks to Linux kernel developers.
Look in to engine dir in the source.

C (more elaborated) variant:
http://zed.karelia.ru/mmedia/bin/edsm-g2-rev-h.tar.gz