views:

65

answers:

2

In a multithreaded Java application, I just tracked down a strange-looking bug, realizing that what seemed to be happening was this:

  • one of my objects was storing a reference to an instance of ServerSocket
  • on startup, one thread would, in its main loop in run(), call accept() on the socket
  • while the socket was still waiting for a connection, another thread would try to restart the component
  • under some conditions, the restart process missed the cleanup sequence before it reached the initialization sequence
  • as a result, the reference to the socket was overwritten with a new instance, which then wasn't able to bind() anymore
  • the socket which was blocking inside the accept() wasn't accessible anymore, leaving a complete shutdown and restart of the application as the only way to get rid of it.

Which leaves me wondering: a) Does the blocking call prevent, or interfere with, GC in any way? b) If the ServerSocket does get GCed, will that make the socket available again?

In general, what are good practices I can follow to avoid this type of bug? For instance, I learned two lessons here:

  • All lifecycle logic (i. e. component level, init-start-stop-cleanup cycles) must be synchronized. Rather obvious, I guess, but I didn't take it seriously enough.
  • Lifecycle logic should be as simple as possible to avoid my problem of non-obvious code paths that skip cleanup or initialization steps.
+2  A: 

You should always close() ServerSocket if you want a predictable cleanup.

The fact that all the references have been dropped only means that it is available for GC, not that it ever will be GC'ed. With available process memory in megabytes, there is a very small chance that a single object will be garbage collected in short period.

Usually you call close from a maintenance thread. In many server apps it would be called from the shutdown hook.

Alexander Pogrebnyak
Just to clarify: Does that mean that once the ServerSocket does get GCed, the socket will be closed? Or, rather, that the blocking accept() is not what prevents GC? Regarding close() - yeah sure, not calling close() under certain circumstances was exactly the mistake I made. One lesson from this was to make the core lifecycle logic simpler.
Hanno Fietz
@Hanno: looks like ServerSocket does not implement `finalize`. Concrete SocketImpl on the other hand may implement `finalize` but I would not rely on it being called for the reasons I've outlined in my posting.
Alexander Pogrebnyak
+2  A: 

To answer the question in the title: nothing will happen to the ServerSocket. There's still a reference to your ServerSocket from the call stack of the thread blocking on the accept() call; this reference will keep the ServerSocket from being eligible for garbage colelction.

Sbodd
Thanks, that's an important part of the answer.
Hanno Fietz