/*
* SocketFlusher.java February 2007
*
* Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.simpleframework.transport;
import static org.simpleframework.transport.TransportEvent.ERROR;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import org.simpleframework.transport.reactor.Operation;
import org.simpleframework.transport.reactor.Reactor;
import org.simpleframework.transport.trace.Trace;
/**
* The <code>SocketFlusher</code> flushes bytes to the underlying socket
* channel. This allows asynchronous writes to the socket to be managed in such
* a way that there is order to the way data is delivered over the socket. This
* uses a selector to dispatch flush invocations to the underlying socket when
* the socket is write ready. This allows the writing thread to continue without
* having to wait for all the data to be written to the socket.
*
* @author Niall Gallagher
*
* @see org.simpleframework.transport.Controller
*/
class SocketFlusher implements Flusher {
/**
* This is the signaller used to determine when to flush.
*/
private Signaller signaller;
/**
* This is the scheduler used to block and signal the writer.
*/
private Scheduler scheduler;
/**
* This is the writer used to queue the packets written.
*/
private Writer writer;
/**
* This is the trace object used to monitor flushing events.
*/
private Trace trace;
/**
* This is used to determine if the socket flusher is closed.
*/
private boolean closed;
/**
* Constructor for the <code>SocketFlusher</code> object. This is used to
* flush buffers to the underlying socket asynchronously. When finished
* flushing all of the buffered data this signals any threads that are
* blocking waiting for the write to finish.
*
* @param reactor
* this is used to perform asynchronous writes
* @param writer
* this is used to write the buffered packets
*/
public SocketFlusher(Socket socket, Reactor reactor, Writer writer)
throws IOException {
this.signaller = new Signaller(writer);
this.scheduler = new Scheduler(socket, reactor, this.signaller, this);
this.trace = socket.getTrace();
this.writer = writer;
}
/**
* Here in this method we schedule a flush when the underlying writer is
* write ready. This allows the writer thread to return without having to
* fully flush the content to the underlying transport. If there are
* references queued this will block.
*/
@Override
public synchronized void flush() throws IOException {
if (this.closed) throw new TransportException("Flusher is closed");
boolean block = this.writer.isBlocking();
if (!this.closed) {
this.scheduler.schedule(block);
}
}
/**
* This is executed when the flusher is to write all of the data to the
* underlying socket. In this situation the writes are attempted in a non
* blocking way, if the task does not complete then this will simply enqueue
* the writing task for OP_WRITE and leave the method. This returns true if
* all the buffers are written.
*/
private synchronized void execute() throws IOException {
boolean ready = this.writer.flush();
if (!ready) {
boolean block = this.writer.isBlocking();
if (!block && !this.closed) {
this.scheduler.release();
}
this.scheduler.repeat();
} else {
this.scheduler.ready();
}
}
/**
* This is used to abort the flushing process when the reactor has been
* stopped. An abort to the flusher typically happens when the server has
* been shutdown. It prevents threads lingering waiting for a I/O operation
* which prevents the server from shutting down.
*/
private synchronized void abort() throws IOException {
this.scheduler.close();
this.writer.close();
}
/**
* This is used to close the flusher ensuring that all of the data within
* the writer will be flushed regardless of the amount of data within the
* writer that needs to be written. If the writer does not block then this
* waits to be finished.
*/
@Override
public synchronized void close() throws IOException {
boolean ready = this.writer.flush();
if (!this.closed) {
this.closed = true;
}
if (!ready) {
this.scheduler.schedule(true);
}
}
/**
* The <code>Signaller</code> is an operation that performs the write
* operation asynchronously. This will basically determine if the socket is
* write ready and drain each queued buffer to the socket until there are no
* more pending buffers.
*/
private class Signaller implements Operation {
/**
* This is the writer that is used to write the data.
*/
private final Writer writer;
/**
* Constructor for the <code>Signaller</code> object. This will create
* an operation that is used to flush the packet queue to the underlying
* socket. This ensures that the data is written to the socket in the
* queued order.
*
* @param writer
* this is the writer to flush the data to
*/
public Signaller(Writer writer) {
this.writer = writer;
}
/**
* This returns the socket channel for the connected pipeline. It is
* this channel that is used to determine if there are bytes that can be
* written. When closed this is no longer selectable.
*
* @return this returns the connected channel for the pipeline
*/
@Override
public SocketChannel getChannel() {
return this.writer.getChannel();
}
/**
* This is used to perform the drain of the pending buffer queue. This
* will drain each pending queue if the socket is write ready. If the
* socket is not write ready the operation is enqueued for selection and
* this returns. This ensures that all the data will eventually be
* delivered.
*/
@Override
public void run() {
try {
SocketFlusher.this.execute();
} catch (Exception cause) {
SocketFlusher.this.trace.trace(ERROR, cause);
this.cancel();
}
}
/**
* This is used to cancel the operation if it has timed out. If the
* delegate is waiting too long to flush the contents of the buffers to
* the underlying transport then the socket is closed and the flusher
* times out to avoid deadlock.
*/
@Override
public void cancel() {
try {
SocketFlusher.this.abort();
} catch (Exception cause) {
SocketFlusher.this.trace.trace(ERROR, cause);
}
}
}
}