@@ -126,13 +126,36 @@ private void addChannel(ChannelN chan) {
126126 _channelMap .put (chan .getChannelNumber (), chan );
127127 }
128128
129+ /**
130+ * Remove the argument channel from the channel map.
131+ * This method must be safe to call multiple times on the same channel. If
132+ * it is not then things go badly wrong.
133+ */
129134 public synchronized void disconnectChannel (ChannelN channel ) {
130135 int channelNumber = channel .getChannelNumber ();
131- if (_channelMap .remove (channelNumber ) != channel )
132- throw new IllegalStateException (
133- "We have attempted to "
134- + "create a disconnect a channel that's no longer in "
135- + "use. This should never happen. Please report this as a bug." );
136- channelNumberAllocator .free (channelNumber );
136+
137+ // Warning, here be dragons. Not great big ones, but little baby ones
138+ // which will nibble on your toes and occasionally trip you up when
139+ // you least expect it.
140+ // Basically, there's a race that can end us up here. It almost never
141+ // happens, but it's easier to repair it when it does than prevent it
142+ // from happening in the first place.
143+ // If we end up doing a Channel.close in one thread and a Channel.open
144+ // with the same channel number in another, the two can overlap in such
145+ // a way as to cause disconnectChannel on the old channel to try to
146+ // remove the new one. Ideally we would fix this race at the source,
147+ // but it's much easier to just catch it here.
148+ synchronized (_channelMap ){
149+ ChannelN existing = _channelMap .remove (channelNumber );
150+ // Nothing to do here. Move along.
151+ if (existing == null ) return ;
152+ // Oops, we've gone and stomped on someone else's channel. Put it back
153+ // and pretend we didn't touch it.
154+ else if (existing != channel ){
155+ _channelMap .put (channelNumber , existing );
156+ return ;
157+ }
158+ channelNumberAllocator .free (channelNumber );
159+ }
137160 }
138161}
0 commit comments