JDK-4075058 : java.io: Add support for non-blocking I/O
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version:
    1.1,1.1.2,1.1.3,1.1.7,1.2.0,1.2.2,1.3.0 1.1,1.1.2,1.1.3,1.1.7,1.2.0,1.2.2,1.3.0
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS:
    generic,solaris_2.5.1,solaris_2.6,windows_95,windows_nt generic,solaris_2.5.1,solaris_2.6,windows_95,windows_nt
  • CPU: generic,x86,sparc
  • Submitted: 1997-08-28
  • Updated: 2001-05-05
  • Resolved: 2001-05-05
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Description
[dougr 28-Aug-97] I am running:

	 java -fullversion
	java full version "jvm111_23n:97.06.09"

Currently there is no poll() equivalent in the
Java-net (or/and IO) classes.

I am developing the Java version of the rpc.cmsd. One of the goals
is 10,000 connections. Estimate FCS in Mar-98.

However, without a poll like feature in the Java classes, I have
to create one thread for each connection. Then in each thread
block in a read system call. This limis to to less than 2,000
connections.

Or I have to snag some of the Socket source code, grab
the socket() fd, and do the poll with a native method.

There are performance problems with one thread per connection
and blocking reads:

  Measured results on a dual-50mhz SS10 64MB:

  1) Java native threads, no poll:

    Almost 7000-8000 Sockets can be open if they do *nothing* at all.
   OR
    About  2000 Sockets can be in a blocking read() call.

    At which point the OS is swaping so much it takes an L1-A to get out.

  2) With a 'C' program and poll() (select is limited to 1024 fd's):

    10,000 Sockets can be open.
   AND
    10,000 Sockets can be in poll() waiting for data.

    and the system is still usable.

  3) With a 'C' program and NOT using poll()

    10,000 Sockets can be open
   AND
    Almost 2,000 Sockets can be in a blocking read() call, then
    the program exits with a error indication that there was no
    more resources (I forgot the exact errno).


Also,
With HTTP 1.1(?) and connections that stay alive, I would think that
the Java-Web-Server will be running into the same problem.


 1998-01-21 ###@###.###

Request from Ed Randall ###@###.### is to add the KEEPALIVE
timeout notifications to this event/listenr mechanism.


Name: mc57594			Date: 01/12/2000


J:\borsotti\jtest>java -version
java version "1.2"
Classic VM (build JDK-1.2-V, native threads)
Consider the following program:

import java.io.*;
public class Th {
    static Thread killer;
    static Thread waster;

    class Killer extends Thread {
        public void run() {
            System.out.println("killer running");
            try {
                sleep(3000);
                System.out.println("killer interrupts waster" +
                    waster.isInterrupted());
                waster.interrupt();
                System.out.println("killer has interrupted waster" +
                    waster.isInterrupted());
            } catch (InterruptedException e) {
                System.out.println("killer interrupted! " + isInterrupted());
            }
            System.out.println("killer DONE!");
        }
    }
    class Waster extends Thread {
        public void run() {
            System.out.println("waster running");
            try {
                FileOutputStream f = new FileOutputStream("tmp.tmp");
                byte[] ba = new byte[10000000];
                f.write(ba);
            } catch (IOException e) {
                System.out.println("waster ioexception!" + isInterrupted());
            }
            System.out.println("waster DONE!");
        }
    }

    public static void main (String[] args) {
        Th t = new Th();
        killer = t.new Killer();
        waster =  t.new Waster();
        waster.start();
        killer.start();
    }
}


when executed it prints:

J:\borsotti\jtest>java Th
waster running
killer running
killer interrupts wasterfalse
killer has interrupted wastertrue
killer DONE!
^C

J:\borsotti\jtest>dir tmp.tmp
 Volume in drive J is \\tlvhgr\dknmpm3
 Volume Serial Number is 0000-0000

 Directory of J:\borsotti\jtest

12/07/99  04:16p            10,000,000 tmp.tmp
               1 File(s)     10,000,000 bytes

in this run, the program has been activated, the
"waster" thread started to write a long file, the
"killer" thread tried to kill it without success,
and the control-C took effect only at the end of
the execution (as shown by the file size).

Apparently, the write() methods is not aborted as
a result of interrupting the executing thread.
A similar test, in which write() was replaced by
read() shows the same behaviour.

This makes impossible to implement applications in
which some control (e.g. a "stop" button) is provided
to allow the user to abort a long i/o operation which
was wrongly started
========================
From: Angelo Borsotti <###@###.###>
To: ###@###.###
Subject: Re: (Review ID: 98744) Incomplete handling of i/o interruption.

Hello Mark,

in my example program, the killer in indeed making
a waster.interrupt(). I.e. it is interrupting a
thread, which is making an io operation.
The purpose, and the meaning of "interruption" has
always been in most system that of making a thread
(process or whatever independently running activity)
which was blocked in some long operation to be
awakened to complete it.
The method sleep() implements it correctly: it stop
sleeping.
Note that ideally, there would be no need for the
concept of interruption if all threads never get blocked
for long time: instead of it they could a sequence of
smaller operation and test in between a flag which could
be set by other threads to interrupt them.
Even a sleep() can be broken in smaller sleep()'s.
This, however, besides being inefficient, it makes also
programs more complex, and is by no means a safe solution
since nobody prevents a thread to make a long operation.
Without interruptions, you have no way to stop a program
which is flooding your terminal with output you no longer
want to see, and that by chance has been written by a 
programmer who did not know that interruptions did not
behave as commonly expected.

I thus should reinforce my request to have interruptions behave
as they should be.

Best Regards

Angelo Borsotti.
(Review ID: 98744)
======================================================================

Comments
EVALUATION This requires significant API changes. It's too late to do this in JDK 1.2. -- mr@eng 1/5/1998 This is being addressed as part of the "New I/O" effort under JSR-51. The new API will appear in Merlin (= J2SE 1.4). Please see 4313882. -- mr@eng 2001/5/5
05-01-1998

SUGGESTED FIX To cope with those large servers, here's the approach I circulated on 5-August-97, with some clarifications: * Use events to indicate that I/O is possible. There are now only two kinds of events that I know we're interested in right now: data is readable, and a socket can be accepted. One could also indicate data is writeable. (Note: SVr4 streams support more options, not all of which are yet visible through Java APIs. The most significant of these is probably out-of-band data.) Works like standard events: - interface InputReadyListener ... normal listener, one method "InputReady" takes InputReadyEvent. - class InputReadyEvent ... source is InputStream - interface AcceptReadyListener ... ditto one method "AcceptReady" takes AcceptReadyEvent. - class AcceptReadyEvent ... source is ServerSocket - interface OutputReadyListener ... ditto one method "OutputReady" takes OutputReadyEvent. - class OutputReadyEvent ... source is OutputStream * Not all input streams would work with this mechanism, only some of them ... specifically, ones associated with sockets, and if someone's being thorough, maybe some kinds of files. (Similarly, output streams and server sockets.) Ability to work with this mechanism would be flagged using an interface which I'll here call "Pollable". (Maybe several of them would be used; not too significant at this level.) That'd be public. * Expose a notification thread abstraction. It'd have the usual {add,remove}*Listener methods for InputReady, OutputReady, and AcceptReady events. The registration to receive the events would be done on this class; I'll sketch it out here (class names not critical): class PollThread extends Thread { public void addInputReadyListener ( PollableInputStream in, InputReadyListener listener ); public void removeInputReadyListener ( PollableInputStream in, InputReadyListener listener ); // etc, for OutputReady, AcceptReady, and any // others. }; Implementation-wise, this would more or less block on poll() or (Win32) WaitMultipleEvents, and when the OS says things are ready, it'd pass that info from C-land to Java-land and then the Java code would perform all the relevant callbacks. The file descriptors would be gotten from those Pollable objects, probably using some "internal" API that exposes the file descriptor as needed for those system calls (but not to any application code -- careful!). It's TBD how to do this on MacOS. THe most portable way to do this on UNIX is with select(), but that doesn't provide the requisite scaling on Soiaris. The first points are the standard Java event model as applied to I/O. The third exposes thread doing the notification since the application is in a lot better position to get the duty cycle right than the JDK internals. That is, to make the choice of how many threads to allocate to handle the I/O in the application system. (The VM shouldn't really be in the business of allocating such threads!) What's different about this approach is that it is a THREE party event model, not a two party model. Otherwise it's standard Java events. david.brownell@Eng 1997-08-28 Here's some discussion put forth by a Java on Sun developer: Jon Winston asked me to share with this list our proposal for a poll/select style interface for Java. Our server can now support a more traditional worker thread model as well as a thread per client model that Java encourages. We don't have any performance numbers yet to know for sure how much this will help our scalability. For a fairly good explanation of the problem we are trying to solve, see the following JavaSoft bug report: http://developer.javasoft.com/developer/bugParade/bugs/4075058.html <http://developer.javasoft.com/developer/bugParade/bugs/4075058.html> This interface is simply a wrapper around the poll system call. There are a few known issues: 1. There is no "supported" way to get the FileDescriptor of a ServerSocket. That would be easy to fix. We work around this by putting our own SocketImplFactory class in the java.net package which then has access to make a PlainSocketImp and save a pointer so we can ask it for its FileDescriptor later. 2. the poll interface isn't very threads friendly. You have one thread basically doing the polling. If one of the worker threads finishes request and wants to return the FileDescriptor to the set of poll file descriptors, it has to wake up the thread blocked in the poll. One way might be to interrupt the thread. We actually have a special loopback socket that we write to make the polling thread wake up because we are nervous about using thread interrupt being basically JDK 1.0.2 developers where it's not really supported. The first thing would be a minor interface change. I can't think of a major reason not to support it. The second issue is a little more troubling. Something like NT I/O completion ports seem a little more thread friendly and more directly support the worker thread model. http://premium.microsoft.com/msdn/library/techart/msdn_scalabil.htm <http://premium.microsoft.com/msdn/library/techart/msdn_scalabil.htm> I'm just as curious to hear about what Weblogic thinks about this as SunSoft, since I'm guessing they may have already gone down a similar path for their products. -bri import java.io.FileDescriptor; import java.io.InterruptedIOException; /** Poll provides a general mechanism for reporting I/O conditions associated with a set of FileDescriptors and for waiting until one or more specified conditions becomes true. Specified conditions include the ability to read or write data without blocking, and error conditions. WARNINGS In some countries, electioneering is illegal within one hundred feet of a polling place. */ public class Poll { /** Data can be read without blocking. For streams, this flag means that a message that is not high priority is at the front of the stream head read queue. This message can be of zero length. */ public static final short POLLIN = 0x0001; /** Data can be written without blocking. For streams, this flag specifies that normal data (not high priority or priority band > 0) can be written without being blocked by flow control. This flag is not used for high priority data, because it can be written even if the stream is flow controlled. */ public static final short POLLOUT = 0x0002; /** An error has occurred on the FileDescriptor. */ public static final short POLLERR = 0x0004; /** The device has been disconnected. For streams, this flag in revents is mutually exclusive with POLLOUT, since a stream cannot be written to after a hangup occurs. This flag and POLLIN are not mutually exclusive. */ public static final short POLLHUP = 0x0008; /** fd is not a valid FileDescriptor. */ public static final short POLLNVAL = 0x0010; /** The fd member of each Poll object specifies an open file descriptor. The poll() method uses the events member to determine what conditions to report for this FileDescriptor. If one or more of these conditions is true, poll() sets the associated revents member. poll() ignores any Poll object whose fd member is null. If the fd member of all Poll objects is null, poll() returns 0 and has no other results. The conditions indicated by POLLNORM and POLLOUT are true if and only if at least one byte of data can be read or written without blocking. The exception is regular files, which always poll true for POLLNORM and POLLOUT. Also, streams return POLLNORM in revents even if the available message is of zero length. The condition flags POLLERR, POLLHUP, and POLLNVAL are always set in revents if the conditions they indicate are true for the specified FileDescriptor, whether or not these flags are set in events. For each call to poll(), the set of reportable conditions for each FileDescriptor consists of those conditions that are always reported, together with any further conditions for which flags are set in events. If any reportable condition is true for any FileDescriptor, poll() returns with flags set in revents for each true condition for that FileDescriptor. If no reportable condition is true for any of the file descriptors, poll() waits up to timeout milliseconds for a reportable condition to become true. If, in that time interval, a reportable condition becomes true for any of the FileDescriptors, poll() reports the condition in the file descriptor's associated revents member and returns. If no reportable condition becomes true, poll() returns without setting any revents bit masks. If the timeout parameter is a value of -1, poll() does not return until at least one specified event has occurred. If the value of the timeout parameter is 0, poll() does not wait for an event to occur but returns immediately, even if no specified event has occurred. The behavior of poll() is not affected by whether the O_NONBLOCK flag is set on any of the specified FileDescriptors. @param fds An array of Poll objects, one for each FileDescriptor of interest. If fds is null, a NullPointerException will be thrown. If an element of fds is null, a NullPointerException will be thrown. The proper way to disable a Poll object is to null the fd field. @param nfds Specifies the number of Poll objects in the fds array. A non-positive value will cause an IllegalArgumentException. @param timeout Specifies the maximum length of time (in milliseconds) to wait for at least one of the specified conditions to occur. @return Upon successful completion, poll() returns a nonnegative value. If the call returns 0, poll() has timed out and has not set any of the revents bit masks. A positive value indicates the number of FileDescriptors for which poll() has set the revents bit mask. If poll() fails, it throws IOException. */ public static int poll (Poll[] fds, int nfds, long timeout) throws InterruptedIOException { if (fds == null) { throw new NullPointerException(); } if (nfds <= 0) { throw new IllegalArgumentException(); } return poll0(fds, nfds, timeout); } /** Convenience method when a timeout is not required */ public static int poll (Poll[] fds, int nfds) throws InterruptedIOException { if (fds == null) { throw new NullPointerException(); } if (nfds <= 0) { throw new IllegalArgumentException(); } return poll0(fds, nfds); } /** The actual internal native call */ private static native int poll0 (Poll[] fds, int nfds, long timeout) throws InterruptedIOException; /** The actual internal native call (no timeout) */ private static native int poll0 (Poll[] fds, int nfds) throws InterruptedIOException; /** FileDescriptor If null, this Poll object will not be considered in a call to poll */ public FileDescriptor fd; /** Requested conditions POLLERR, POLLHUP and POLLNVAL are always implicitly requested. */ public short events; /** Reported conditions POLLERR, POLLHUP and POLLNVAL are always implicitly requested and so may appear in revents even if they were not explicitly listed. revent should be restored to zero by the caller before calling poll again. */ public short revents; /** Create a Poll object */ public Poll () { } /** Create a Poll object This takes a events as an int but internal peforms the cast to short. This is to make life easier on the programer since the bit operators traffic in ints. */ public Poll (FileDescriptor fd, int events) { this.fd = fd; this.events = (short)events; } static { System.loadLibrary("poll"); init(); } private static native void init (); } al.thompson@eng 07-23-1998
21-04-0014