Actual Suggestion For How To Do Asynch I/O Notification (And Then Some)

(A Java bug report by Dan Bornstein)


Product: Java 2 SDK, Standard Edition (J2SE 1.2.x)
Hardware Platform: Platform Independent
Category: Feature Request

Synopsis: Actual Suggestion For How To Do Asynch I/O Notification 
          (And Then Some)

Product Version: Java 2 SDK, Standard Edition, v.1.3 [FCS]
Subcategory: classes_lang: Packages java.lang, java.lang.reflect, java.lang.ref
Operating System: generic

Development Kit or Runtime version: based on review of the 1.3 docs

Description:

[This note is being sent to the Java 2 Standard API Comments address, to
the JSR-000051 ("New I/O APIs for the Java Platform") comments address, to
Mark Reinhold (the official contact person for JSR-000051) as well as being
submitted as an RFE to the bug database. Apologies for the scattershot
approach, but it was unclear to me which route would have caused me to hit
the most appropriate people to comment and/or act on this proposal.]

Hi. First of all, let me say I'm very happy that Sun is pursuing a
relatively open process for evolving Java. Now to the meat.

There has been a lot of discussion, outcry, etc., surrounding the fact that
Java doesn't have (unistd-style) select() functionality. I wholeheartedly
agree that it's a major problem that one can't wait on the completion of
multiple I/O requests simultaneously from a single thread, but I think the
problem runs a little deeper. Therefore, the solution is actually more
general.

It's great that there's work being done on asynchronous I/O now
(JSR-000051), but I'm sad to say that there is no public draft available
for review. If there were, perhaps my comments could be a bit more precise.
The thing I want to suggest is that the framework that you use for I/O
completion notification be modeled on the notification done in the
java.lang.ref package. The system set up for the Reference class and its
subclasses is wonderful in several respects, but the most salient one is
that it leaves it totally up to the developer, in terms of thread usage,
how to actually notice the events it deals with. The salient design points
are:

* Queuing a notification (in particular, putting a Reference on a 
ReferenceQueue) requires no allocation and never executes user code.
Therefore it is safe to perform this operation at a fairly low level,
without necessarily creating/using new threads for queuing operations.

* The interface to notification access (that is, the ReferenceQueue
instance methods) allow for both polling and waiting.

If clients want to poll for notifications in some other event loop (or
wherever), they can do that. If they want to set up a single thread, then
that's trivial too. Or, if they want to manage a pool of threads looking at
a queue, then they can do that too. (I only say this because I saw a
comment in the bugbase that suggested that the right way to deal with this
is to have a factory pattern, so that your special notification factory
could instantiate threads--or not--as appropriate. I get a rash whenever I
hear that someone wants to add new global state to the system, in this case
yet another settable default factory. I hope you do too.)

In the case of java.lang.ref, there's an obvious object that is destined to
land on a queue, namely the Reference object. However, in the I/O case,
it's not so clear, for a couple of reasons. First, unlike with References,
there's more than one salient action on an object (Reader, Writer, Socket,
etc.) which could cause a notification, e.g., if you have two pending UDP
sends (which could complete out-of-order), if all you knew was that the
socket got added to the queue, you couldn't know which send it was that
completed. Second, there's the matter that there is no superclass or
superinterface for all I/O operations, more importantly the former, since
that's where one would otherwise put the link slots needed for enqueuing
(since one would want to avoid allocation during a queue operation).

So, what I suggest is to add a new class, Queue (a la ReferenceQueue, but
not specific to References), and a new class, Queueable (a la Reference),
which is the thing that ends up on Queues. Queueable has link fields and
(perhaps implicitly) a Queue that it is associated with (that is, a
Queueable can only be associated with one Queue at a time), and nothing
else; like References, subclasses can add whatever they need. Unlike
References, I'd like to be able to reassociate a Queueable with a new
queue, so that object recycling becomes a possibility. Then, all you do is
add a Queueable argument to the asynchronous I/O methods. When the I/O
completes, it enqueues the Queueable (a short, atomic operation in which
user code *cannot* be executed) and then you're done with it.

    public class Queue
    {
        private Queueable enqueuedHead;
        private Queueable enqueuedTail;
        private Queueable nonEnqueuedHead;
        private Queueable nonEnqueuedTail;

        public Queue () {...}
        public Queueable poll () {...}
        public Queueable remove () {...}
        public Queueable remove (long timeout) {...}
    }

    public class Queueable
    {
        // three cases:
	// * linked into an enqueued chain if it's been enqueued
	// * linked into a nonEnqueued chain if it hasn't been enqueued
        //   but is associated with a Queue
        // * both link fields null if it's not associated with a queue
	private Queueable prev;
	private Queueable next;

        /** starts out associated with no queue */
	public Queueable () {...}
	/** starts out associated with the given queue */
	public Queueable (Queue queue) {...}
        final public boolean isEnqueued () {...}
        final public boolean enqueue () {...}
        final public boolean dequeue () {...}
        final public void setQueue (Queue queue) {...}
    }

Let me note at this point that the Queue/able code itself would actually
not be specific to I/O, and it might even make sense to refactor
java.lang.ref.Reference to inherit from Queueable and then make
java.lang.ref.ReferenceQueue a trivial subclass of Queue (to maintain
binary compatibility) and deprecate it. However, there may be some gc
interaction issues which force it to remain separate.

Let me also note that one probably-extremely useful method would be one
which does the impedence matching between wait/notify and enqueuing,
something like these:

    static public void queueWhenNotified (Queueable queueable,
                                          Object obj);

    static public void queueWhenNotified (Queueable queueuable,
                                          Object obj,
                                          Queueable timeoutQueueable,
                                          long timeout);

where the behavior would be to add a notation to the given object such that
if it gets notified (that is, something calls Object.notify or notifyAll on
it), then the given Queueable gets enqueued. In the second case, after the
given timeout, if the object hasn't been notified then the timeoutQueueable
gets enqueued. (The notify vs. notifyAll distinction would stil hold in the
expected way; that is, a Queueable would always be enqueued in response to
a notifyAll, but only when its "turn" came up in the case of normal
notify.)

If Java had all this, then there would truly be a unified way to deal with
asynchronous notifications from any source. No one would complain about
missing select(), because what they'd have would be way more powerful and
much easier to use as well.

Thanks for listening.

Dan Bornstein
danfuzz@milk.com

Work Around: Spawn threads like there's no tomorrow. That, or do icky
native code stuff, and we know that that's not the way to 100% Pure Java
nirvana.

Impact on User: Some progress is possible without resolving this bug.


May 24, 2000

The Java Rants
Copyright © 2000 Dan Bornstein, all rights reserved.