-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
proposal: Go 2: sends on closed channels do not panic #21985
Comments
Under this proposal, incorrect code that broke before will silently fail, that is, appear to run correctly while still being incorrect. That seems retrograde. |
how about just like type assertion, if ok is present, then not panic, otherwise, panic just like Go 1. |
If a program sends on a closed channel, it's not managing resources correctly. You could argue that your proposal makes it easier to do that, but I would counter that it makes it too easy to construct poor concurrent structures. |
Also, let's make integer division by zero silently return a random number. /s |
I think there are a few approaches that could be considered in order to address this issue. The first is that static analysis tools could be used to highlight to users where in their code they are not checking the return value of the send. There's a precedence for this already with linters such as errcheck that find unchecked errors. A second, more heavy-handed approach, would be for the compiler to reject any code that does not check the return value of a send, so that
My only hesitation with this approach is that I can't think of any other places in the language where
I'm not sure that I agree that it would make it much easier to construct poor concurrent structures given that it's already possible to emulate this behavior with an additional lock around the channel, as the |
The later form is invalid. |
My apologies, I should have been more explicit, I was referring to function calls that have a single return value. Updated my comment to reflect that.
You're right! Thanks! I had blindly assumed it would work the same as a function call. Updated my comment appropriately. |
Here's an actual example where this feature would be useful. I've got some code where a set of goroutines are managing data that is made available over an HTTP interface. When the HTTP code receives a request, if finds a channel to the relevant goroutine in a global When data is deleted, the goroutine that manages it first removes the channel from the I don't see any clean way to solve this without locking. Currently, I don't bother, and live with the occasional panic. |
@jech you don't need to close the channels, you can use an extra channel (called it Here are some examples: http://www.tapirgames.com/blog/golang-channel-closing |
Sure, I can make the structure of my code increasingly complex. Or I could simply write
in the reader passed to |
As others have pointed out, the language already has all the tools you need to achieve your goal, and despite your protests it's really not much more complex to use them. And as I said before, your proposal makes it too easy to write bad concurrent structures. It's analogous to how reentrant locks result in bad locking protocols. |
...despite your protests...
your proposal makes it too easy to write bad concurrent structures.
Just to avoid any blame getting misdirected -- there's two bodies here.
The proposal is due to @jeromefroe, who remained impeccably polite
throughout the discussion. It's only me that's rudely protesting.
|
Good or bad, whether the ok is present will also make effect on the behaviors of select blocks. |
Forgive me if I'm beating a dead horse, but it's not clear to me why this proposal makes it too easy to write bad concurrent structures. Admittedly though, I don't have much experience with reentrant locks, so I didn't quite see how the analogy applies. My thought with this proposal was that since channels serve as synchronization points, one can use a closed channel as a way to cancel in-flight goroutines. For example, a program may have a source of goroutines (a server which creates a new goroutine for each connection being one example) that perform some processing and then put a value on a channel. When an event occurs the program may want to close the source, so no new goroutines are created, and the channel as well, so that any in-flight goroutines are cleaned up. |
I wrote previously:
Looking at this some more, there's a race condition: if the (asynchronous) channel is closed just after the request is written, the request is discarded with no error indication to the caller. So I'm slowly starting to feel that @robpike is right — this proposal would lead to bad code. |
Wouldn't that be equivalent to closing a buffered channel that has values in it though? The values are still in the channel and available to be pulled off. |
Change https://golang.org/cl/86515 mentions this issue: |
Change https://golang.org/cl/86555 mentions this issue: |
Change https://golang.org/cl/86555 mentions this issue: |
I just found another problem to this "sends on closed channels panic" problem when working with "M receivers, N senders and X closers channel" problem. "close the channel" and "send message to it with panic recover" in two different goroutines is a data race condition to the golang current data race detecter. And the "close an additional signal channel" workaroud is difficult to work right when I need read all the data from the channel after the channel closed. So if I can use sync.Mutex to solve my problem,I will not use channel. It is more complex then I first thought. The workaround https://golang.org/cl/86555 looks good to me. |
There are good arguments against this proposal above, and few supporters. Declining. |
@robpike (et al) |
I don't understand any of the arguments against this proposal. Closing a channel is currently an accepted way of sending a message from the sender to the receiver, saying that they won't be sending any more data down that channel. But what about if you want to send a message from the receiver to the sender, that you won't be reading any more data from the channel? Take https://github.com/emersion/go-imap, which has messages := make(chan *imap.Message, 10)
done := make(chan error, 1)
go func() {
done <- c.Fetch(seqset, items, messages)
}()
for msg := range messages {
if prs, err := mdb.IsMsgIdPresent(msg.Envelope.MessageID); err != nil {
// STOP PROCESSING
} else if prs {
// ...
}
} At the moment, the only reliable way to implement Now yes, Let me respond to comments:
No, it won't (at least, not the current version): Old code will be
That assumes one specific use of a channel. I think the above way of using channels is perfectly legitimate. I don't see the difference between saying "if a program sends on a closed channel, it's not managing resources correctly" and saying "if a program receives on a closed channel, it's not managing resources correctly". "Correctly" depends on the model.
The same thing could be said about receiving on closed channels. All the other objections have been answered as far as I can tell. So the only concrete objection boils down to this unsubstantiated assertion:
Please consider re-opening this issue. |
I think a lot of the confusion about this could be resolved by removing two-way channels. If you only have a |
@DeedleFake Not sure you understood my comment. In my example, there's no confusion -- I did my research, and discovered that while I can do an atomic "receive if not closed" operation, I can't do an atomic "send if not closed" operation. The channel is one way. The producer was asked to send a bunch of data down the channel. The consumer consumes some of it, and realizes he wants to stop the whole transaction. Why should I have an entire second channel on the off chance I need to cancel the operation? Or alternately -- why bother allowing a "receive if not closed" operation? After all, you could just have two channels: one for the data, one for the stop signal, and Given that "receive if not closed" is already implemented, really I don't understand the resistance to implementing "send if not closed". The same arguments for one are arguments for the other; the same arguments against one are arguments against the other. |
My comment wasn't a direct response to yours, per se. It was more just a general comment on this discussion. I've been hit by this as well and it took me a while to realize the reason was that I was trying to use channels in both directions, and that the type system seems to default towards encouraging that, even though you really probably shouldn't do that.
That wouldn't work the same way, though, even if both are being used the same direction as each other. If you close a buffered channel, the receiving end doesn't see that it's closed until the buffer's empty. If you used two channels, one of which is buffered, and selected, then you'd have a random chance of noticing the closed channel in the The biggest problem I usually run into with sending on closed channels comes from trying to have multiple senders on a single channel. In that case, you do need to use a second channel, because even if you signaled the receiving end by closing the channel and used a // Sender:
select {
case <-doneSending:
return
case mainChannel <- data:
// This panics in secondary senders when mainChannel gets closed.
}
// Receiver:
for data := range mainChannel {
// Do stuff.
} The best ways to do this seem to be to |
The key point here is that closing a channel is considered to be a send operation. We need a synchronous point at which you say "there will be no more data on this channel" in order to implement You are suggesting repurposing
Yes. The model is: |
Like I said earlier, I think the confusion arises primarily in the case of two-way channels. In that case, the language allows you to send data back and forth, so both ends are inherently both senders and receivers simultaneously. Even if you think of a |
If a |
In general we don't use issues for questions, especially not closed issues. Please see https://golang.org/wiki/Questions. Thanks. The answer to your question is that a closed channel is never ready to send a value on, so the select statement will never choose that case. |
Thanks @ianlancetaylor . From the spec of July 31, 2019:
I posted here because the spec as written supports the original proposal here. |
Motivation
A common pattern I've seen is using a lock to protect a channel and a variable which indicates whether or not the channel is closed. For example:
Using this approach one has to acquire two locks in each call to
Enqueue
. The first lock being the read lock forq.closed
and the second being the internal lock used by the channel. However, the channel already has a fieldclosed
to indicate whether the channel has been closed and a lock which protects it, so the extra lock is only necessary because there is no publicly accessible API for determining if the channel is closed.Proposal
To support cases like the one above, where a channel may be closed concurrently with sends on it, I'd like to propose that Go add support for conditionally sending values on a channel by returning a
bool
from send operations to indicate whether the send was successful. Sends to a channel could then do the following:Callers that do, in fact, want the send to panic could explicitly call panic themselves. While those that want to treat such a case as an error will have that ability as well.
Thanks!
The text was updated successfully, but these errors were encountered: