Proposal: expose tea.msg to handle multithreading scenarios #294
Replies: 5 comments
-
have you consider checking out this example? which allows the modal to subscribe your own channel, which allows you to use standard channel management pattern (so you can perform non blocking send, etc etc) focus on waitForActivity function which returns a |
Beta Was this translation helpful? Give feedback.
-
I'm pretty sure that example will non-deterministically drop commands when processing effectful messages at the same time as exiting the program. More concretely: In my application, I have a logger goroutine, which, depending on if the tea app is running or not, will log them through the tea app via So, in order to detect when the tea program has exited and log to stdout, we do this: select {
case bubbleTeaMessageChannel <- someLoggerMessageStruct:
case <- loggerHasExited:
// log via stdout instead, and set a flag to never log through bubbletea again
} Where go func() {
tea.Start()
loggerHasExited<-true
} In the proposed solution, in this sequence of events, we drop a message:
Note that also, since the command itself is blocking, the select here blocks before going into selection, and does not close until from the same select spec:
In fact, I think since that example always tries to have an open |
Beta Was this translation helpful? Give feedback.
-
Apologies for the wall of text. That example is analogous to my current solution, which runs the loop outside of tea. I've spent a lot of time digging through exit scenarios for it, and it will also drop a message and a leak a goroutine in a similar order of operations. |
Beta Was this translation helpful? Give feedback.
-
I see the problem now, since there will be at least one Thinking about the hanging routine gave me an idea, of using(or rather abusing)
This seems to work fine most times at least, but due to point 2, when it is in a high volume application, there still is the possibility of dropped messages, in that case it's probably due to when both |
Beta Was this translation helpful? Give feedback.
-
Yeah, none of these workarounds do well in systems with tight timings. It's also introducing a lot of complexity into the user program, and I'm not 100% sure to what end. I'd love to just delete our current workaround and use channels, since that seems to be the design go is forcing |
Beta Was this translation helpful? Give feedback.
-
I'm using bubbletea as a persistent UI in a logger.
The logger has its own main loop that receives messages from multiple routines, and in that loop, calls
tea.Send()
on some subset of messages.Unfortunately, if the program self-exits, there's a possibility that the loop deadlocks if a message is received by the main loop before we know the tea program has exited or not, because
tea.Send()
blocks when there is no receiver on the other side of the channel.The canonical go solution would to do something like this
there's a similar problem if you try to Quit() a dead tea program.
I was wondering if it makes sense to expose the tea message channel so it could be selected on.
Spitballing on alternatives, I thought for a bit about if this would be possible to implement internally without exposing the message channel, but I think if multiple routines are calling
tea.Send()
, the only way to guarantee that all of them recieve theteaExited
message is if a goroutine that infinitely writes toteaExited
is leaked on program quit, which doesn't seem acceptable.Or, if you try to react to
teaExited
in Send() and set a flag, then we either have a possible race with simultaneous writes after the program exits, or have to put a lock around every call to tea.Send(). And locking on every Send() seems like it would be really bad for perf to me.Beta Was this translation helpful? Give feedback.
All reactions