Please criticize my library: mint - tiny event emitter built on generics
github.com/btvoidx/mintPosted Mar 9, 2023, 9:15 AM UTC
Posted Mar 9, 2023, 9:15 AM UTC
The after hook can probably be dropped since on will function the same? If I’m thinking correctly, there’s no meaningful happens-before relationship where the order matters – once the channel send has occurred there’s no guarantee of which order consumers pull out of the channel.
You could provide both a callback api and a channel api though for convenience, but then why not make the callback generic? Also it’s probably worth documenting whether the callbacks will block the flow of the message events for other consumers.
Also document what happens with multiple consumers of the same type. Is it pseudorandom “queue groups” or fan out? Do slow consumers block everyone else?
Consumers have each their own channel. Emit spawns a goroutine for each consumer, so the order in which they receive events is whatever at that point.
Slow consumers only block Emit and therefore AfterEmit hook, and this is the reason why it exists: if only fires after all consumers got the value.
[deleted]
I’m also building a library for pubsub mechanisms. https://github.com/alexandria-oss/streams (Still WIP).
When I started to build it I needed to deal with graceful shutdowns so in-flight messages/processing at shutdown are not lost. I see that you don’t actually handle that scenario (AFAIK). That is one crucial topic to consider IMO.
Oh, it is very easy to add it with mint.Use. You can wrap sync.WaitGroup and add BeforeEmit and AfterEmit so they call wg.Add(1) and wg.Done() respectively. Then you grab an instance of your new WaitGroup wrapper, add it to an emitter via mint.Use, and then you can do the regular wg.Wait() to ensure that all in-flight messages have reached their consumers. This one instance will even work for any number of emitters.
Yeap, using a WaitGroup does the job. Anyways, Don't you think that should be part of your library? Not missing messages are fundamental in a PubSub bus.
No, I don't think it should be a part of the library. It was made to be barebones, and I want to keep it that way. But I will consider adding new hooks to mint.Use if existing ones prove insufficient. For example, there is no way to track subscriber count, so it could be a hook.
Nice library & very simple, but why do you have to write
e.Emit(e, MyEvent{Msg: "Hi", FromID: 1})whene.Emit(MyEvent{Msg: "Hi", FromID: 1})looks better since you already havee?Functions on a receiver cannot be generic in go, at least for now. Unless you make the whole receiver generic as well, but then you can only emit messages of 1 type on it.
True
Exactly what u/lorx said. Consumers do not pull events themselves from some kind of queue,
Emitpushes them out, so it has to be generic to know what should everyone get.But shouldn't that be
mint.Emit(e, MyEvent{...})rather thane.Emit(e, MyEvent{...})? Ifeis anEmitterthat doesn't seem to come with any methods.Also a link to the godoc in your README is a good idea. Somebody else beat me to it, possibly even you :), so it was already loaded up by https://pkg.go.dev/github.com/btvoidx/mint , but I still wouldn't expect people to know that generally.
If you don't know about this, you may find Testable Examples in Go a good read. Testable examples can avoid this problem, though you can't embed them straight in the README of course, only a link.
I would suggest documenting explicitly what happens if a receiver blocks. It looks to me like it would jam the emission process. I believe this is a perfectly acceptable behavior in the Go ecosystem and I'm not complaining about that. ("Fixing" it is non-trivial, and there's suddenly a lot of choices to make; it can't be done one-size-fits-all.) I just would suggest explicitly documenting it as such.
I would even consider it debatable where the go statement here does anything useful. To be clear, it is not wrong. It just isn't too clear to me that it does anything useful, because nowadays, you've really got two cases; either you've got nicely-behaved consumers consuming very well and quickly, or you've got at least one pathological consumer that will block up the entire process. Counterintuitively, the boundary between these two cases gets sharper and sharper as machines and programs scale up (sadly, by virtue of more and more of the percentage of the available space becoming pathological). The space of possibilities where the program is just in enough trouble that the
gowill save you, but not in so much trouble that it's just totally boned anyhow, is surprisingly small. But like I said, it's not wrong.Oh, I didn't even know about existence of Testable Examples! Will take a look.
I have changed documentation of
Emitto specify that consumers blockEmitandAfterEmithook, but do not block each other. Now I now wonder whether it is should be specified somewhere in the README along the first example.