Added exception handling in the main loop for the web server to avoid the main loop thread getting killed off due to an unexpected/unhandled exception.

Moved code for JSON handling and metadata/metadata container into the Common project from the Foundation project.
This commit is contained in:
wcrisman
2014-09-16 14:01:31 -07:00
parent 1a8fd62dd8
commit b48e81bfe0
39 changed files with 930 additions and 994 deletions

View File

@@ -1851,170 +1851,180 @@ public class WebServer {
Debug.log(e);
}//catch//
if(key != null) {
final boolean isWrite = key.isWritable();
final ChannelContext context = (ChannelContext) key.attachment();
final SelectableChannel channel = key.channel();
if(channel instanceof ServerSocketChannel) {
try {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) channel;
SocketChannel socketChannel = serverSocketChannel.accept();
ServerSocketContext serverSocketContext = (ServerSocketContext) context;
SocketContext socketContext = new SocketContext(serverSocketContext);
socketChannel.configureBlocking(false);
socketChannel.socket().setSendBufferSize(SEND_BUFFER_SIZE);
socketChannel.socket().setReceiveBufferSize(RECEIVE_BUFFER_SIZE);
socketContext.key = socketChannel.register(selector, SelectionKey.OP_READ, socketContext);
socketContext.serverSocketContext = serverSocketContext;
//Debug.log("Connection opened to " + socketChannel.socket().getInetAddress() + ":" + socketChannel.socket().getPort());
if(serverSocketContext.serviceListener.type != IServiceListener.TYPE_SSL) {
socketContext.socketReadBuffer = ByteBuffer.allocate(BUFFER_SIZE);
}//if//
if(debug) {
Debug.log("Connection opened to " + socketChannel.socket().getInetAddress() + ":" + socketChannel.socket().getPort());
}//if//
}//try//
catch(Throwable e) {
//TODO: Can we recover?
Debug.log(e);
}//catch//
}//if//
else if(channel instanceof SocketChannel) {
// boolean socketClosed = false;
//Toggle the write or read flag.//
synchronized(key) {
// //Save the ops that will be set when the processing is complete.//
// ((AbstractSocketContext) context).setFlags(key.interestOps());
//Notes: Java (pre-jdk7) does not have the ability to read and write to a socket at the same time (two threads, one socket). Post jdk7 there is AsynchronousSocketChannel and AsynchronousServerSocketChannel which could be used to send/receive at the same time.
//Truely enabling Speedy would require a thread to read which when finished would flag read again BEFORE processing the message and BEFORE sending a response.
//For now (so we don't have to require jdk7 yet) we will simply allow Speedy to queue up messages, but only read, process, and then write them one at a time. Most of the speed loss is in the waiting for the WRITE to finish before handling the next request (due to it being broken into packets and the mechanics of TCP), and that is generally minimal (speed lose) since usually the bottleneck in speed is the browser's connection to the internet (most of us haven't got Gigabit Ethernet at home). Anyone with enough home juice to have this be a problem would only notice the difference for really porky websites (which is a problem in and of its self).
key.interestOps(key.interestOps() ^ (isWrite ? SelectionKey.OP_WRITE : SelectionKey.OP_READ));
//Not allowing either reads or writes to continue until all processing of this message is done.//
// key.interestOps(0);
}//synchronized//
if(((SocketChannel) channel).isOpen()) {
ThreadService.run(new Runnable() {
public void run() {
boolean socketClosed = false;
try {
if(isWrite) {
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
synchronized(((AbstractSocketContext) context).getLock()) {
//Process the pending write to the socket as much as is possible, then return.//
((AbstractSocketContext) context).processResponses();
}//synchronized//
}//if//
else {
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
synchronized(((AbstractSocketContext) context).getLock()) {
//Process the incoming request and send the response (a partial response may be sent in which case the socket will be set to wait for a write opportunity and not a read opportunity).//
((AbstractSocketContext) context).processRequest();
}//synchronized//
}//else//
}//try//
catch(TlsFailureException e) {
//Allow the failure to be ignored. This occurs when the client fails to use TLS or fails to send the host name as part of the TLS handshake.//
try {((SocketChannel) channel).close();}catch(Throwable e2) {} //Release the socket so the message doesn't continue to be processed.//
}//catch//
catch(Throwable e) {
if(debug) Debug.log(e);
//Force the socket to be closed (for sure).//
try {((SocketChannel) channel).close();} catch(Throwable e2) {}
//Debug.log(e);
socketClosed = true;
}//catch//
finally {
if(channel != null && !socketClosed && channel.isOpen() && context != null) {
selector.wakeup();
}//if//
else if(channel != null && (!channel.isOpen() || socketClosed) && channel instanceof SocketChannel && context instanceof SocketContext) {
cleanupClientChannel((SocketContext) context, (SocketChannel) channel);
}//else if//
}//finally//
}//run()//
});
/*
try {
synchronized(this) {
// if(++activeThreadCount != maxThreadCount) {
//Start another thread to take this thread's place.//
ThreadService.run(this);
// }//if//
}//synchronized//
try {
if(key != null) {
final boolean isWrite = key.isWritable();
final ChannelContext context = (ChannelContext) key.attachment();
final SelectableChannel channel = key.channel();
if(channel instanceof ServerSocketChannel) {
try {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) channel;
SocketChannel socketChannel = serverSocketChannel.accept();
ServerSocketContext serverSocketContext = (ServerSocketContext) context;
SocketContext socketContext = new SocketContext(serverSocketContext);
if(isWrite) {
// if(debug) {
// ((SocketContext) context).debugBuffer.append("Socket is now write available.\n");
// Debug.log("Socket is write available: " + ((SocketChannel) channel).socket().getInetAddress() + ":" + ((SocketChannel) channel).socket().getPort());
// }//if//
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
synchronized(((AbstractSocketContext) context).getLock()) {
//Process the pending write to the socket as much as is possible, then return.//
((AbstractSocketContext) context).processResponses();
}//synchronized//
socketChannel.configureBlocking(false);
socketChannel.socket().setSendBufferSize(SEND_BUFFER_SIZE);
socketChannel.socket().setReceiveBufferSize(RECEIVE_BUFFER_SIZE);
socketContext.key = socketChannel.register(selector, SelectionKey.OP_READ, socketContext);
socketContext.serverSocketContext = serverSocketContext;
//Debug.log("Connection opened to " + socketChannel.socket().getInetAddress() + ":" + socketChannel.socket().getPort());
if(serverSocketContext.serviceListener.type != IServiceListener.TYPE_SSL) {
socketContext.socketReadBuffer = ByteBuffer.allocate(BUFFER_SIZE);
}//if//
if(debug) {
Debug.log("Connection opened to " + socketChannel.socket().getInetAddress() + ":" + socketChannel.socket().getPort());
}//if//
else {
// if(debug) {
// ((SocketContext) context).debugBuffer.append("Socket is now read available.\n");
// Debug.log("Socket is read available: " + ((SocketChannel) channel).socket().getInetAddress() + ":" + ((SocketChannel) channel).socket().getPort());
// }//if//
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
synchronized(((AbstractSocketContext) context).getLock()) {
//Process the incoming request and send the response (a partial response may be sent in which case the socket will be set to wait for a write opportunity and not a read opportunity).//
((AbstractSocketContext) context).processRequest();
}//synchronized//
}//else//
}//try//
catch(TlsFailureException e) {
//Allow the failure to be ignored. This occurs when the client fails to use TLS or fails to send the host name as part of the TLS handshake.//
try {((SocketChannel) channel).close();}catch(Throwable e2) {} //Release the socket so the message doesn't continue to be processed.//
}//catch//
catch(Throwable e) {
if(debug) Debug.log(e);
//Force the socket to be closed (for sure).//
try {((SocketChannel) channel).close();}catch(Throwable e2) {}
//Debug.log(e);
socketClosed = true;
//TODO: Can we recover?
Debug.log(e);
}//catch//
finally {
boolean requiresWakeup = false;
if(channel != null && !socketClosed && channel.isOpen() && key != null && context != null) {
requiresWakeup = true;
}//if//
else if(channel != null && (!channel.isOpen() || socketClosed) && channel instanceof SocketChannel && context instanceof SocketContext) {
cleanupClientChannel((SocketContext) context, (SocketChannel) channel);
}//else if//
//Loop if the last thread to wait for a message couldn't start another thread due to the max number of threads allowed.//
synchronized(this) {
// if(activeThreadCount-- != maxThreadCount) {
loop = false;
if(requiresWakeup) {
selector.wakeup();
}//if//
// }//if//
}//synchronized//
}//finally//
*/
}//if//
}//else if//
}//if//
else if(channel instanceof SocketChannel) {
// boolean socketClosed = false;
//Toggle the write or read flag.//
synchronized(key) {
// //Save the ops that will be set when the processing is complete.//
// ((AbstractSocketContext) context).setFlags(key.interestOps());
//Notes: Java (pre-jdk7) does not have the ability to read and write to a socket at the same time (two threads, one socket). Post jdk7 there is AsynchronousSocketChannel and AsynchronousServerSocketChannel which could be used to send/receive at the same time.
//Truely enabling Speedy would require a thread to read which when finished would flag read again BEFORE processing the message and BEFORE sending a response.
//For now (so we don't have to require jdk7 yet) we will simply allow Speedy to queue up messages, but only read, process, and then write them one at a time. Most of the speed loss is in the waiting for the WRITE to finish before handling the next request (due to it being broken into packets and the mechanics of TCP), and that is generally minimal (speed lose) since usually the bottleneck in speed is the browser's connection to the internet (most of us haven't got Gigabit Ethernet at home). Anyone with enough home juice to have this be a problem would only notice the difference for really porky websites (which is a problem in and of its self).
key.interestOps(key.interestOps() ^ (isWrite ? SelectionKey.OP_WRITE : SelectionKey.OP_READ));
//Not allowing either reads or writes to continue until all processing of this message is done.//
// key.interestOps(0);
}//synchronized//
if(((SocketChannel) channel).isOpen()) {
ThreadService.run(new Runnable() {
public void run() {
boolean socketClosed = false;
try {
if(isWrite) {
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
synchronized(((AbstractSocketContext) context).getLock()) {
//Process the pending write to the socket as much as is possible, then return.//
((AbstractSocketContext) context).processResponses();
}//synchronized//
}//if//
else {
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
synchronized(((AbstractSocketContext) context).getLock()) {
//Process the incoming request and send the response (a partial response may be sent in which case the socket will be set to wait for a write opportunity and not a read opportunity).//
((AbstractSocketContext) context).processRequest();
}//synchronized//
}//else//
}//try//
catch(TlsFailureException e) {
//Allow the failure to be ignored. This occurs when the client fails to use TLS or fails to send the host name as part of the TLS handshake.//
try {((SocketChannel) channel).close();}catch(Throwable e2) {} //Release the socket so the message doesn't continue to be processed.//
}//catch//
catch(Throwable e) {
if(debug) Debug.log(e);
//Force the socket to be closed (for sure).//
try {((SocketChannel) channel).close();} catch(Throwable e2) {}
//Debug.log(e);
socketClosed = true;
}//catch//
finally {
if(channel != null && !socketClosed && channel.isOpen() && context != null) {
selector.wakeup();
}//if//
else if(channel != null && (!channel.isOpen() || socketClosed) && channel instanceof SocketChannel && context instanceof SocketContext) {
cleanupClientChannel((SocketContext) context, (SocketChannel) channel);
}//else if//
}//finally//
}//run()//
});
/*
try {
synchronized(this) {
// if(++activeThreadCount != maxThreadCount) {
//Start another thread to take this thread's place.//
ThreadService.run(this);
// }//if//
}//synchronized//
if(isWrite) {
// if(debug) {
// ((SocketContext) context).debugBuffer.append("Socket is now write available.\n");
// Debug.log("Socket is write available: " + ((SocketChannel) channel).socket().getInetAddress() + ":" + ((SocketChannel) channel).socket().getPort());
// }//if//
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
synchronized(((AbstractSocketContext) context).getLock()) {
//Process the pending write to the socket as much as is possible, then return.//
((AbstractSocketContext) context).processResponses();
}//synchronized//
}//if//
else {
// if(debug) {
// ((SocketContext) context).debugBuffer.append("Socket is now read available.\n");
// Debug.log("Socket is read available: " + ((SocketChannel) channel).socket().getInetAddress() + ":" + ((SocketChannel) channel).socket().getPort());
// }//if//
//Prevent another thread from reading/writing on the same socket at the same time (safety). This would have to be removed if SPEEDY (or similar pipelining) were allowed, and AsynchronousSocketChannel/AsynchronousServerSocketChannel would have to be used (requiring jdk7).//
synchronized(((AbstractSocketContext) context).getLock()) {
//Process the incoming request and send the response (a partial response may be sent in which case the socket will be set to wait for a write opportunity and not a read opportunity).//
((AbstractSocketContext) context).processRequest();
}//synchronized//
}//else//
}//try//
catch(TlsFailureException e) {
//Allow the failure to be ignored. This occurs when the client fails to use TLS or fails to send the host name as part of the TLS handshake.//
try {((SocketChannel) channel).close();}catch(Throwable e2) {} //Release the socket so the message doesn't continue to be processed.//
}//catch//
catch(Throwable e) {
if(debug) Debug.log(e);
//Force the socket to be closed (for sure).//
try {((SocketChannel) channel).close();}catch(Throwable e2) {}
//Debug.log(e);
socketClosed = true;
}//catch//
finally {
boolean requiresWakeup = false;
if(channel != null && !socketClosed && channel.isOpen() && key != null && context != null) {
requiresWakeup = true;
}//if//
else if(channel != null && (!channel.isOpen() || socketClosed) && channel instanceof SocketChannel && context instanceof SocketContext) {
cleanupClientChannel((SocketContext) context, (SocketChannel) channel);
}//else if//
//Loop if the last thread to wait for a message couldn't start another thread due to the max number of threads allowed.//
synchronized(this) {
// if(activeThreadCount-- != maxThreadCount) {
loop = false;
if(requiresWakeup) {
selector.wakeup();
}//if//
// }//if//
}//synchronized//
}//finally//
*/
}//if//
}//else if//
}//if//
}//try//
catch(java.nio.channels.CancelledKeyException e) {
//Occurs if the socket is closed while we are handling the key.//
Debug.log(e); //TODO: Does anything need doing here? Should it be ignored?
}//catch//
catch(Throwable e) {
Debug.log(e);
//TODO: There needs to be more specfic error handling if we got here.
}//catch//
}//while//
}//run()//
}//NetworkListener//