Hi John, Thank you!
The h.closed channel, if checked properly (after the RLock), prevents the race condition that Augusto pointed out in his post a few back. I fixed my implementation here: https://play.golang.org/p/QTkSJAOPtE Thanks again, Evan On Tue, 13 Sep 2016 at 14:04 John Souvestre <j...@souvestre.com> wrote: > OK. Give me a minute to add that. I just wanted to make sure I was > headed in the right direction. J > > > > Note: In looking at your original code I didn’t see any way that the > error could happen, so I ignored that case. Given this, there was no need > for the h.closed channel. > > > > Back in a few. J > > > > John > > John Souvestre - New Orleans LA > > > > *From:* Evan Digby [mailto:evandi...@gmail.com] > *Sent:* 2016 September 13, Tue 15:59 > *To:* John Souvestre; golang-nuts > > > *Subject:* Re: [go-nuts] Having difficulty testing this "cleanly" > > > > Hi John, > > > > What you've posted is a valid way to implement the handler, but not a way > to validate it. > > > > The implementation in the example isn't the problem. It's how to validate > the implementation with a test. > > > > If we add a WaitGroup.Wait inside the handler then the test is not valid > because it will wait until they're done. If the test does the waiting, then > we aren't validating that the implementation itself does the waiting. > > > > I'm trying to find a clean way to validate that the waiting is done by the > Close call. > > > > Thanks again for your effort in this! > > > > Evan > > > > On Tue, 13 Sep 2016 at 13:52 John Souvestre <j...@souvestre.com> wrote: > > Hi Evan. > > > > I still don’t quite understand exactly what you are shooting for. I tried > to reimplement what you posted originally. Check out > https://play.golang.org/p/koUJYCKFpa. Does this come close functionally? > > > > John > > John Souvestre - New Orleans LA > > > > *From:* golang-nuts@googlegroups.com [mailto:golang-nuts@googlegroups.com] > *On Behalf Of *Evan Digby > *Sent:* 2016 September 13, Tue 15:32 > *To:* golang-nuts > *Cc:* aro...@gmail.com > *Subject:* Re: [go-nuts] Having difficulty testing this "cleanly" > > > > Hi John/Egon/Augusto, > > > > I should point out is that all we need to guarantee (barring abnormal > termination of course) is that once a task starts processing, it finishes. > Partially processed messages are bad, but http requests that don't result > in a message being processed at all are okay. > > > > We don't need to guarantee that the result of every Accept in the HTTP > server results in a processed message. We handle this on the OPS side by > ensuring we stop sending requests to that instance before terminating the > process. We just want to make sure, at that point, that the messages which > did make it to the handler are flushed. > > > > So the case where: > > > > h.Handle(...) <-- gets past the closed channel check, calls go ..., butthe > goroutine doesn't execute yet. > h.Close() <-- closes the close channel, Locks and Unlocks,returns. > ...now the goroutine executes and acquires the read lock. > > > > We actually don't care if "Handle" completes in this example. We only care > if that our task handler starts processing a message that it completes the > processing. > > > > Thanks again, > > Evan > > > On Tuesday, 13 September 2016 13:24:03 UTC-7, aro...@gmail.com wrote: > > The mutex approach is fundamentally broken because you can't guarantee > that the tasks are all started (and have a read-lock acquired) before you > call close. > > > > Consider: > > h.Handle(...) <-- gets past the closed channel check, calls go ..., > butthe goroutine doesn't execute yet. > h.Close() <-- closes the close channel, Locks and Unlocks,returns. > ...now the goroutine executes and acquires the read lock. > > > > So really, if you can't control the Handle() function, you need two > WaitGroups: one to verify that all goroutines have started before shutting > down the task handler and a second one for all goroutines to have > finished. However, it's tricky if we don't know the real use case. > > > Sounds like you are trying to do graceful http shutdown. Have you looked > at other libraries that do that? If you don't have a way to account for > the time between Handle(..) is called and the goroutine starts, you always > might miss a task that got called near the time Close() was called. > > > > - Augusto > > > On Tuesday, September 13, 2016 at 12:50:50 PM UTC-7, Evan Digby wrote: > > Hi Aroman, > > > > Your approach using the WaitGroup is definitely better in this toy > example. The reason I didn't use the WaitGroup is because the non-toy > example is wrapping the HTTP Server handler. I have no way to inject an > "add" before the goroutine is created since that's handled by Go's HTTP > Server without re-implementing the accept->handle loop using the listener. > > > > Apologies for not giving the full context in the example. > > > > I'm not sure how it could block an outstanding task since the closed > channel is called before the Lock(), so no additional calls to RLock will > be made at that point, and the Lock will just wait until all of the RLocks > are complete. > > > > Regarding your testing strategy, I do like it better than any of my > current strategy; however, There still is a chance that a task could > complete between lines 90 and 91: > > > > h.Close() > > events <- ALL_TASKS_FINISHED > > > > So this doesn't solve the racy-ness I'm concerned about unless you put an > arbitrary sleep in the handlers, which I'm trying to avoid. > > > On Tuesday, 13 September 2016 12:34:17 UTC-7, aro...@gmail.com wrote: > > The WaitGroup is better than the lock approach, since the lock approach > could block an outstanding task. The key to using waitgroups is to call > Add() outside of goroutines that might call done: > > > > https://play.golang.org/p/QVWoy8fCmI > > On Tuesday, September 13, 2016 at 12:19:16 PM UTC-7, Evan Digby wrote: > > Hi John, > > > > Thanks for the reply. I've tried many incarnations that include > WaitGroups; however, none seem to achieve the desired result. > > > > If I add a WaitGroup with a defer done in the handler, and then wait after > the Close() then the test itself implements the requirement and won't > protect from future refactors. There's no way to test that a WaitGroup is > done without waiting for it, and even if there was it would be racy because > between the Close() and WaitGroup wait call tasks could complete. If I > wrapped the wait and the done in goroutines to see which one happened > first, also racy. > > > > If you have something else in mind can you elaborate on how it would help > in this case? > > > > Thanks again! > > > > Evan > > > On Tuesday, 13 September 2016 12:01:29 UTC-7, John Souvestre wrote: > > Have you considered using a sync.WaitGroup? > > > > John > > John Souvestre - New Orleans LA > > > > *From:* golan...@googlegroups.com [mailto:golan...@googlegroups.com > <golan...@googlegroups.com>] *On Behalf Of *Evan Digby > *Sent:* 2016 September 13, Tue 13:56 > *To:* golang-nuts > *Subject:* [go-nuts] Having difficulty testing this "cleanly" > > > > Has anyone come across a good way, non-racy way to ensure that N tasks are > guaranteed to be completed after a function is called? Essentially I have a > “Close” function that must be guaranteed to block until all tasks are > finished. Achieving this was pretty simple: wrap each task in an RLock, and > then a Lock on close. > > > > Example: https://play.golang.org/p/7lhBPUhkUE > > > > Now I want to write a solid test to guarantee Close will meet that > requirement of all tasks must finish first for posterity. In that example, > try commenting out the RLock/RUnlock on lines 25/26. You'll see that it no > longer outputs many, if any, lines. I'm trying to prevent that from > happening in the future by some cowboy refactor! > > > > All of the ways I can come up with involve Sleeping or launching more > tasks than I _think_ can be finished in time--obviously not good! > > > > I feel like I must be missing some obvious way to test this and I'll end > up feeling silly once someone replies with the solution. I'm okay with that! > > -- > You received this message because you are subscribed to the Google Groups > "golang-nuts" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to golang-nuts...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. > > -- > You received this message because you are subscribed to the Google Groups > "golang-nuts" group. > > To unsubscribe from this group and stop receiving emails from it, send an > email to golang-nuts+unsubscr...@googlegroups.com. > > > For more options, visit https://groups.google.com/d/optout. > > -- > You received this message because you are subscribed to a topic in the > Google Groups "golang-nuts" group. > To unsubscribe from this topic, visit > https://groups.google.com/d/topic/golang-nuts/jh-nvt9ukBg/unsubscribe. > To unsubscribe from this group and all its topics, send an email to > golang-nuts+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. > > -- > You received this message because you are subscribed to a topic in the > Google Groups "golang-nuts" group. > To unsubscribe from this topic, visit > https://groups.google.com/d/topic/golang-nuts/jh-nvt9ukBg/unsubscribe. > To unsubscribe from this group and all its topics, send an email to > golang-nuts+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.