June 30, 2020
On Tuesday, 30 June 2020 at 00:33:41 UTC, Ali Çehreli wrote:
> On 6/29/20 4:34 PM, aberba wrote:
>
> > So with this, without the Thread.sleep() to block main from
> exiting, the
> > spawned thread  will terminate immediately.
>
> You can call core.thread.thread_joinAll at the end of main.
So I tried that initially but my (){ writeln(...) } wasn't printing anything in console. Could that be related to stdout buffering? The program kept running though.


>
> Another way would be to wait for a worker's exit by looking for LinkTerminated but you need to start the thread with spawnLinked:

Read that too, but doesn't seem like the desired behavior I want.


So here's the thing, unlike JavaScript, the D behavior seems to be like a while(bool){} has to be placed directly within the scope of main. Was thinking as long my while loop in setInterval() was running, spawn() will be kept alive.

It seem both vibe.d and arsd have a similar setInterval() implementation and they both require using their event loop to keep the program alive.

In my case, wanted setInterval() to behave like it's own event loop without placing it in a while(bool){} loop in main()

June 30, 2020
On Tuesday, 30 June 2020 at 08:15:54 UTC, aberba wrote:
> On Tuesday, 30 June 2020 at 00:33:41 UTC, Ali Çehreli wrote:
>> On 6/29/20 4:34 PM, aberba wrote:
>>
>> > So with this, without the Thread.sleep() to block main from
>> exiting, the
>> > spawned thread  will terminate immediately.
>>
>> You can call core.thread.thread_joinAll at the end of main.
> So I tried that initially but my (){ writeln(...) } wasn't printing anything in console. Could that be related to stdout buffering? The program kept running though.

Seems weird. This works great on my machine:


import core.time : Duration, msecs;
import core.thread : Thread, thread_joinAll;
import std.concurrency : spawn, Tid, send, receiveTimeout;
import std.stdio : writeln;

private struct IntervalStop {}

Tid setInterval(Duration dur, void function() fn) {
    return spawn((Duration d, void function() f){
        while (!receiveTimeout(d, (IntervalStop s){})) {
            f();
        }
    }, dur, fn);
}

void stopInterval(Tid tid) {
    tid.send(IntervalStop());
}

void main() {
    auto a = setInterval(1000.msecs, (){ writeln("Hello from spawned thread A"); });
    // Stop it before it happens
    stopInterval(a);
    Thread.sleep(2000.msecs);

    auto b = setInterval(1000.msecs, (){ writeln("Hello from spawned thread B"); });
    // Let this one run a little
    Thread.sleep(2500.msecs);
    stopInterval(b);

    auto c = setInterval(1000.msecs, (){ writeln("Hello from spawned thread C"); });
    // Sending the wrong message doesn't make it happen or stop prematurely
    c.send("Stop this at once!");
    Thread.sleep(2500.msecs);
    stopInterval(c);

    thread_joinAll();
}

So I guess the error is elsewhere, but I'm not sure where and how.

--
  Simen
June 30, 2020
On Tuesday, 30 June 2020 at 12:48:32 UTC, Simen Kjærås wrote:
> On Tuesday, 30 June 2020 at 08:15:54 UTC, aberba wrote:
>> On Tuesday, 30 June 2020 at 00:33:41 UTC, Ali Çehreli wrote:
>>> On 6/29/20 4:34 PM, aberba wrote:
>>>
>>> > So with this, without the Thread.sleep() to block main from
>>> exiting, the
>>> > spawned thread  will terminate immediately.
>>>
>>> You can call core.thread.thread_joinAll at the end of main.
>> So I tried that initially but my (){ writeln(...) } wasn't printing anything in console. Could that be related to stdout buffering? The program kept running though.
>

>
> So I guess the error is elsewhere, but I'm not sure where and how.

Yeah, you're right. I changed receiveTimeout() to receive() to try something and forgot to change it back.

Jeez, I hate myself.

Thanks.


So how can I now hide the core.thread.thread_joinAll so the library user doesn't have to type it themselves in main() ? I don't see how that can be done.

June 30, 2020
On 6/30/20 9:44 AM, aberba wrote:
> On Tuesday, 30 June 2020 at 12:48:32 UTC, Simen Kjærås wrote:

>>
>> So I guess the error is elsewhere, but I'm not sure where and how.
> 
> Yeah, you're right. I changed receiveTimeout() to receive() to try something and forgot to change it back.
> 
> Jeez, I hate myself.
> 
> Thanks.
> 
> 
> So how can I now hide the core.thread.thread_joinAll so the library user doesn't have to type it themselves in main() ? I don't see how that can be done.
> 

I assume you need something more than thread_joinAll, because you need to stop all the threads from executing also.

So wrapping this up into a single call would be what you use (it's OK to ask the user to clean up a library manually).

-Steve
June 30, 2020
On Tuesday, 30 June 2020 at 13:44:38 UTC, aberba wrote:
> On Tuesday, 30 June 2020 at 12:48:32 UTC, Simen Kjærås wrote:
>> On Tuesday, 30 June 2020 at 08:15:54 UTC, aberba wrote:
>>> On Tuesday, 30 June 2020 at 00:33:41 UTC, Ali Çehreli wrote:
>>>> On 6/29/20 4:34 PM, aberba wrote:
>>>>
>>>> > So with this, without the Thread.sleep() to block main from
>>>> exiting, the
>>>> > spawned thread  will terminate immediately.
>>>>
>>>> You can call core.thread.thread_joinAll at the end of main.
>>> So I tried that initially but my (){ writeln(...) } wasn't printing anything in console. Could that be related to stdout buffering? The program kept running though.
>>
>
>>
>> So I guess the error is elsewhere, but I'm not sure where and how.
>
> Yeah, you're right. I changed receiveTimeout() to receive() to try something and forgot to change it back.
>
> Jeez, I hate myself.
>
> Thanks.
>
>
> So how can I now hide the core.thread.thread_joinAll so the library user doesn't have to type it themselves in main() ? I don't see how that can be done.

__gshared Tid mainTid;
static this() {
    if (mainTid.tupleof[0] is null) {
        mainTid = thisTid;
    }
}
static ~this() {
    if (thisTid == mainTid) {
        thread_joinAll();
    }
}

The above code does the trick.

So, what does it do? __gshared means 'this variable is accessible to all threads'. static this() runs upon creation of any thread including the main thread. Since the main thread will run first*, it gets to store its Tid in mainTid, and every other thread will see a populated mainTid and leave it alone. In the module destructor, which runs after main(), we call thread_joinAll() iff we're the main thread.

Now, why should you not do this? Well first, instead of getting a tidy crash you get a process that doesn't end. Second, there's the race conditions described below. Third, there's the principle of least astonishment. D programmers expect that when main() returns, the program will exit shortly(ish), while this zombie could continue running indefinitely.

--
  Simen


*I'm pretty sure this is possibly wrong, if a module constructor spawns a new thread. There's also a possible race condition where newly spawned modules may conceivably not see a properly initialized mainTid.
June 30, 2020
On 6/30/20 10:15 AM, Simen Kjærås wrote:
> On Tuesday, 30 June 2020 at 13:44:38 UTC, aberba wrote:
>> On Tuesday, 30 June 2020 at 12:48:32 UTC, Simen Kjærås wrote:
>>> On Tuesday, 30 June 2020 at 08:15:54 UTC, aberba wrote:
>>>> On Tuesday, 30 June 2020 at 00:33:41 UTC, Ali Çehreli wrote:
>>>>> On 6/29/20 4:34 PM, aberba wrote:
>>>>>
>>>>> > So with this, without the Thread.sleep() to block main from
>>>>> exiting, the
>>>>> > spawned thread  will terminate immediately.
>>>>>
>>>>> You can call core.thread.thread_joinAll at the end of main.
>>>> So I tried that initially but my (){ writeln(...) } wasn't printing anything in console. Could that be related to stdout buffering? The program kept running though.
>>>
>>
>>>
>>> So I guess the error is elsewhere, but I'm not sure where and how.
>>
>> Yeah, you're right. I changed receiveTimeout() to receive() to try something and forgot to change it back.
>>
>> Jeez, I hate myself.
>>
>> Thanks.
>>
>>
>> So how can I now hide the core.thread.thread_joinAll so the library user doesn't have to type it themselves in main() ? I don't see how that can be done.
> 
> __gshared Tid mainTid;
> static this() {
>      if (mainTid.tupleof[0] is null) {
>          mainTid = thisTid;
>      }
> }
> static ~this() {
>      if (thisTid == mainTid) {
>          thread_joinAll();
>      }
> }
> 

First, you can just use shared static dtor, as this runs once at the end of the program. At the very least, you can run the setting of mainTid in a shared constructor to avoid the race conditions (also no need to check if its set already).

Second, I realized, thread_joinAll is already being done by the runtime:

https://github.com/dlang/druntime/blob/67618bd2dc8905ad5dee95f3329109aebd839b74/src/rt/dmain2.d#L226

So the question really becomes -- why is it necessary to call thread_joinAll in main?

It's because the main thread's TLS static destructor is closing the owner mailbox, which is sending a message to all the threads that the owner is terminated, causing your threads to exit immediately.

See here: https://github.com/dlang/phobos/blob/268b56be494cc4f76da54a66a6960fa7e527c4ed/std/concurrency.d#L223

Honestly though, I think this is the correct behavior -- if you exit main, you are expecting the program to not hang indefinitely.

-Steve
July 01, 2020
On Tuesday, 30 June 2020 at 14:43:40 UTC, Steven Schveighoffer wrote:
> On 6/30/20 10:15 AM, Simen Kjærås wrote:
>> [...]

My thinking is I don't want regular consumers using the package to think about the technicality of thread_joinAll() at all.

Thinking about putting it in a mixin like:

mixin KeepRunning;

Or something
July 01, 2020
On 7/1/20 2:41 AM, aberba wrote:
> On Tuesday, 30 June 2020 at 14:43:40 UTC, Steven Schveighoffer wrote:
>> On 6/30/20 10:15 AM, Simen Kjærås wrote:
>>> [...]
> 
> My thinking is I don't want regular consumers using the package to think about the technicality of thread_joinAll() at all.
> 
> Thinking about putting it in a mixin like:
> 
> mixin KeepRunning;
> 
> Or something

How about main() starts a thread that starts all the other threads? Then, thread_joinAll() would go inside the non-main :) thread.

However, Steve is right: When main() exits, all threads will and should exit.

Ali

1 2
Next ›   Last »