So here's an example that I'm not sure how it would work with nurseries. Let's say I'm writing some sort of logging functionality for a larger codebase. The API in my library is `log(message)` where message is some random message object. It just appends the message to a file, but if the file size has gotten large enough then it does some slow work, maybe it batches things up and sends them off somewhere or it rotates the log files or whatever.
So in normal Go, I can make this log function do some slow IO, call `go log(message)`, and I'm set. Or I can make the goroutine created inside the log function when it's needed, either way.
With nurseries, where do I create a nursery? I don't want the code that calls log to have to wait for the logging to complete. So the caller shouldn't own a nursery and pass it to the logging library. The logging library can't create a nursery when it needs to do the occasional slow operation because the caller will have to wait for that too, if I understand correctly.
So, do I need to just use some other abstraction entirely, or how would I implement this with nurseries? This almost reminds me of using Java threadpools... you can more or less do anything you want to do with them, you just need to create lots of queues and consumer objects and it takes several abstractions to implement what could just be a single abstraction.
> One of Trio's weirdest, most controversial decisions is that it takes the position that the existence of a background task is not an implementation detail, and should be exposed as part of your API. On balance I think this is the right decision, but it's definitely a bit experimental and has some trade-offs.
There's some argument in there about why that's the case - the high-order argument, I think, is that when I'm done calling some function that uses your logging library, I might be surprised at some later point by seeing my code inside your logging setup. (Maybe the log server dropped the connection and now you're doing a CPU-intensive SSL handshake to reconnect to it, or something.)
I think the idiomatic way to do this would be that, yes, the code that calls log() should provide a nursery somehow, potentially by asking its own caller to provide a nursery. There's an example in that SO answer of a class that accepts a nursery in its constructor, plus a convenience wrapper that creates a nursery and uses the class once (and then waits on tasks to complete before returning).
If you really, really want the operation to happen in the background, there is https://trio.readthedocs.io/en/stable/reference-lowlevel.htm... , but it's bad style to go sticking random things there. At the very least, document this as part of your API.
You could implement the log calls as channel writes. Then you could have a long running log writer task read from the channel in a similar way to the accept() example?
Edit: After reading the docs it seems like you make a logging nursery and then your log calls throw tasks into that nursery?
So in normal Go, I can make this log function do some slow IO, call `go log(message)`, and I'm set. Or I can make the goroutine created inside the log function when it's needed, either way.
With nurseries, where do I create a nursery? I don't want the code that calls log to have to wait for the logging to complete. So the caller shouldn't own a nursery and pass it to the logging library. The logging library can't create a nursery when it needs to do the occasional slow operation because the caller will have to wait for that too, if I understand correctly.
So, do I need to just use some other abstraction entirely, or how would I implement this with nurseries? This almost reminds me of using Java threadpools... you can more or less do anything you want to do with them, you just need to create lots of queues and consumer objects and it takes several abstractions to implement what could just be a single abstraction.