[prev in list] [next in list] [prev in thread] [next in thread] 

List:       rxtx
Subject:    Re: [Rxtx] port in use exception
From:       Gregg Wonderly <gergg () cox ! net>
Date:       2006-09-27 15:52:28
Message-ID: 451A9E3C.9030803 () cox ! net
[Download RAW message or body]

Joachim Buechse wrote:
> I always appreciate a discussion based on good arguments;-)
> 
> I know that Linus T. has argued several times that non-blocking close  
> does not work. However in those cases where it does not work on the  
> kernel level it can not be made to work on the application level  
> neither. In my experience it is impossible to explain to a user why  
> an application "hangs" on close. However they do understand that it  
> may hang on open.

I think part of the issue is whether there is any real value added by waiting 
till (re)open to tell the user that their serial port is hung, or to do that by 
blocking on close.

>>Well, serial data can flow slowly compared to what a program can  
>>generate. So,
>>it should be possible for an application to open a port at 300baud,  
>>send some
>>large amount of data and block on the close, waiting for the send  
>>to complete.
> 
> This is the one point where I really disagree. Port.close() should  
> never be used in the meaning of OutputStream.flush(). Port.close() is  
> the only abort mechanism available. The native implementation should  
> be free to throw away unsend data on close, abort reads and release  
> the system resource as soon as possible.

This is not part of the API contract, and not what developers or users would 
expect.  If there is a need to exit prematurely, and the application is dealing 
with a slow data rate, it can use queuing designs to minimize the amount of data 
that is being written for each write call, based on baud rate, to create a 
suitably short delay.

	boolean done;
	public synchronized void stopNow() {
		done = true;
	}

	public void writeBuffer( byte[]buf ) {
		int i = 0;
		int remain = buf.length;
		int cnt = baud/10;
		while( remain > 0 ) {
			synchronized(this) {
				if( done )
					throw new InterruptedException(remain+" bytes not written");
			}
			int wrcnt = Math.min( cnt, remain );
			int tot = write( buf, i, wrcnt );
			if( tot <= 0 ) {
				throw new IOException(
					"write failed with: "+tot );
			}
			remain -= tot;
		}
	}

>>> From a user perspective, closing a resource means "I lost all
>>>interest in you". In the case where the (synchronous) close is
>>>immediate that is no problem. However in the case where it is not,
>>>this creates big problems. The application by itself has basicly no
>>>means of dealing with a blocking close. As the user has lost interest
>>>in the port he will not understand any kind of dialog regarding an
>>>already closed port, he might have even decided to close the
>>>application and see that it "hangs" on close.
>>
>>It is possible for a java application to do
>>
>>Thread th = new Thread() {
>>	public void run() {
>>		try {
>>			port.close();
>>		} catch( Exception ex ) {
>>			log.log( Level.WARNING, ex.toString(), ex );
>>		}
>>	}
>>};
>>th.setDaemon( true );
>>th.run();
>>
> 
> 1st problem) This returns immediately (even in the case where close  
> does not block and returns within a few seconds). Hence the  
> application has no idea of the progress of the close, it has to join  
> the close thread or even have a callback interface to get status  
> updates. This is what I often do, but it is neither elegant nor simple.

That's why I mentioned the use of a Future later.  You can put this in a Future 
implementation and call Future.get() to indicate a rendevous with close().  A 
more interesting thing is to create a port redevous object that you use to say 
that a port is in use, and this allows multiple threads to wait to use it again.

public class PortRendevous extends OutputStream
		implements Future<PortRendevous > {
	CommPortIdentifier port;
	boolean opened;
	boolean cancelled;
	public PortRendevous( CommPortIdentifier port ) {
		this.port = port;
	}

	public boolean cancel( boolean inter ) {
		synchronized( this ) {
			cancelled = true;
			if( inter ) {
				notifyAll();
			}
		}
		return !opened;
	}

	public synchronized PortRendevous get( long val, TimeUnit unit)
				 throws InterruptedException {
		cancelled = false;
		if( unit == TimeUnit.SECONDS )
			val *= 1000;
		if( opened && !cancelled ) {
			try {
				wait( val );
			} catch( InterruptedException ex ) {
			}
		}
		if( cancelled ) {
			throw new InterruptedException(
				"get "+port+" cancelled");
		}
		return this;
	}

	public synchronized PortRendevous get() throws InterruptedException{
		cancelled = false;
		while( opened && !cancelled ) {
			wait();
		}
		if( cancelled ) {
			throw new InterruptedException(
				"get "+port+" cancelled");
		}
		return this;
	}

	public boolean isCancelled() {
		return cancelled;
	}

	public boolean isDone() {
		return opened;
	}

	public synchronized OutputStream open(String name, int timeout)
			throws IOException {
		// Wait for opened to be false
		get();

		// Open the stream.
		OutputStream out =
			new FilteredOutputStream( port.open(name,timeout) ) {
			public void close() {
				super.close();
				synchronized(PortRendevous.this) {
					opened = false;
					PortRendevous.this.notify();
				}
			}
		};

		// if no exceptions, we get to here, and so set opened=true
		opened = true;

		// return the output stream.
		return out;
	}	
}

Somewhere in your application you then have.

Map<String,PortRendevous>ports = new HashMap<String,PortRendevous>();

public OutputStream openPort( String name, int timeout ) {
	PortRendevous r = ports.get( name );
	OutputStream os = r.open( name, timeout );
	try {
		... do something ...
	} finally {
		os.close();
	}
}

Which will always block on the open, waiting for this thread to be the only user 
of the port.  When the port is closed, the FilteredOutputStream.close() override
will reset the opened status, and notify the waiters to let the next thread go.

Because there is a future involved, a GUI component can track the status of the 
port open, ask isCancelled(), call cancel() etc to manage the activity of the 
threads accessing the port etc.  All using exiting APIs that can be coded to
create a different interface, without having to recode/change RXTX over time.

> 2nd problem) The application has to keep track of ports which are in  
> the state of beeing closed if it wants to reuse/reopen the same ports  
> (race conditions).

The Future implementation above takes care of that.

> 3rd problem) From what I have seen the java "process" will not unwind/ 
> return if a java thread hangs in a kernel call (daemon or not). That  
> problem most likely affects my prefered solution as well, but the  
> above code "suggests" otherwise.

The Java spec says that Daemon threads can not keep the JVM from exiting.

>>...implementation detail about networking.  Serial ports don't have  
>>the same
>>negociated close.  Close progresses, unimpeded, when the write  
>>buffer is empty.
> 
> In my experience the kernel level close may block if the USB driver  
> is trapped in a weird situation (ie a client device not reacting). It  
> may block even if no data remains to be send, I consider this a  
> kernel/driver/device bug - but unfortunately changes to the kernel/ 
> driver/device are often impossible.

These are driver bugs, like it or not.  Covering them up, doesn't expedite the 
users relief of the problem, it only moves the experience to a later or 
different sequence of events.  Production systems may need to work around these 
at the application layer.  RXTX doesn't need to burden itself with anything 
related to these bugs as long as it provides a direct path to the kernel/OS 
functions, the user can create the same workarounds that you would in RXTX, and 
they inherit the associated mess instead of all users of RXTX having to be aware 
of certain behaviors that fall out of certain scenarios that were coded to work 
around bugs in the drivers.

>>  If remote flow control is asserted, there is no negociation for  
>>relief.  This
>>is why it seems interesting, to let the close happen  
>>asynchronously.  In single
>>threaded programming environments, it becomes very convenient to do  
>>this, but I
>>don't think it's a correct behavior.  When serial hardware/software  
>>is broken,
>>and the flowcontrol never subsides, the application can hang forever.
> 
> I have seen cases where unplugging a USB device at the "right moment"  
> or a buggy device that stops responding leads to a close blocking for  
> hours. This is nothing the application can influence. Arguing, that  
> an application should be allowed to hang if the OS/driver/hardware  
> has bugs works in theory but not in (my) practice. I have seen cases,  
> where (only) quitting the Java VM will unwind a hanging kernel close.  
> I can not explain this behaviour, but I can reproduce it with a buggy  
> USB device.

I agree that this can be very frustrating.  My main statement/argument is that 
the more software that RXTX puts between the user and the OS, the more the user 
has to live with in terms of undesired behaviour.  The javax.comm API is simple 
in design, I believe, and that means that as a user, I have less to have to deal 
with in terms of unwanted or unneeded behaviour.  Adding support for OS specific 
ioctls might be nice to support with a property based mechanism so that you 
could ask for the properties for a port, and then be able to see implementation 
specific options that you could change on a particular OS.  But, building the 
API up with more methods that "do nothing" or return undependable results, will 
make it impossible to write dependable application code.  If I can see the 
"properties", I can write code like

	List p = port.getPropertyNames();
	if( p.contains(PortProperties.HW_TYPE).indexOf("buggy name") >= 0 ) {
	    if( p.contains(PortProperties.ASYNC_CLOSE) == false ) {
		throw new IOException("Can't support port: "+port );
	    }
	}

to tell the user that this combination is not going to work with my software. 
But, RXTX or the javax.comm API doesn't have to contain anything explicit
about this issue.

Gregg Wonderly
_______________________________________________
Rxtx mailing list
Rxtx@qbang.org
http://mailman.qbang.org/mailman/listinfo/rxtx
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic