-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IDLE can't be interrupted using idleTerm() #15
Comments
Please post the output of your sample code along with the raw message log ( |
Here are the logs:
|
I also tried the patch for @iragsdale but I think there's still a race condition somewhere :( |
Hmm, I've been using that code for quite a while handling a lot of clients. What doesn't appear to be working correctly? |
I retried and when used properly, it works :) |
Yeah, a better API overall might be to have the idle command take a channel that you can send to for canceling. I think I tried to make that work and had a hard time with it, but it was a while back and I don't recall. |
The problem that you guys are running into is that it's not safe to call IdleTerm concurrently with Recv. The documentation says: "The Client and its Command objects cannot be used concurrently from multiple goroutines." You have one goroutine blocked in the Recv(-1) call and another one in IdleTerm, which also calls Recv via cmd.Result(OK). Only one will get the command completion response, while the other will remain blocked. You aren't fixing this problem by removing the cmd.Result(OK) call. It's still not safe to call IdleTerm while a Recv is in progress. You would need to add mutexes in a number of places to ensure that any outstanding Recv calls are actually blocked on network I/O and are not in the middle of processing a new response. I started out with such design, but it quickly turned into a mess, so I went with the simpler implementation. The best way to deal with this is to Recv for a short interval (e.g. 100ms), or to poll for new responses, and call IdleTerm from the same goroutine when you want to stop idling. |
When coroutine are executing concurrently, I don't think we need mutex. We need probably need mutexes only when it's running in a thread. |
Run that code with the race detector on. I've never tried it, but I'm betting that it will complain about a lot of things. The rest of the client state is unprotected, starting with concurrent access to c.tags and c.cmds. What you're doing is definitely not safe and will break in strange, unpredictable, and unreproducable ways later on. 100ms is an eternity for a CPU and there is nothing wrong in doing that. |
My concern is that if we run ten thousands (or more) IDLE loops, 100ms won't look like a small number any more. I'm going to investigate the race detector. Thanks! |
The race detector isn't showing anything. |
@dinhviethoa I do believe you want to take precautions to use this safely. What we do looks something like the following:
This could probably be cleaned up a bit, but what you see here is that if we hit the timeout, we trigger the end of the IDLE by sending the command to the server. After that we wait for the main thread receive to finish before reading the result and allowing the main thread to continue. That means the ONLY place we can use the client concurrently is to trigger send the end of the idle command. We've run this against the race checker with no issue, and we've been using this to watch many thousands of accounts concurrently with good results and no crashes. It's a bit tricky to use properly, and I'm sure we could find some way to encapsulate that pattern a bit better, but I can confirm it works. |
I just pushed a new interrupt branch, which I think may be the better solution to your problem. I haven't tested these changes, but the idea is to allow one goroutine to interrupt a blocked Recv call in another goroutine by sending an error via a channel. You would still need to use mutexes, conditions, or other channels to synchronize both goroutines so that only one is calling Client methods at any given time, but this should allow you to block on Recv indefinitely. Test it out, let me know what you think. If you like this interface, I'll add some tests for it and merge it into master. I need to think about this a bit more, though. It's been a long time since I last looked at this code. |
Thanks! I'm taking a look at it! |
At first glance, this makes WAY more sense to me. I think it should be far simpler to use and also solves for the case where you send the term to the server but it isn't responding (which currently would continue to block the main thread indefinitely). |
I just tried it. It works really well! Here's an API suggestion: Usage:
Approximate implementation:
|
@iragsdale are you planning to merge that change in https://github.com/boxer/go-imap ? |
Maybe? We're not really doing any active development on the tool that uses this now, as it's currently running pretty stable and doing what we need it to do. |
Late to the party, but I'm also running into this issue. |
Here's a sample code:
When calling, c.IdleTerm() it will interrupt IDLE but the execution flow will be blocked at c.IdleTerm().
The log "interrupted done" won't show.
@iragsdale has a suggestion of fix here:
boxer@796926b
And it seems like it's the right thing to do:
The text was updated successfully, but these errors were encountered: