2008年7月18日 星期五

Standalone JAX-WS is not ready for production

Further to the bug 'light weight http server connections leaks when calling invalid wsdl' 6712353, another problem we encountered is that the standalone jax-ws server doesn't work well when serving concurrent web service calls.

I tried to perform a stress test by making concurrent web service calls, the standalone web service server will keep throwing javax.xml.stream.XMLStreamException.

Is it a bug? Not sure. I will post it to java forum.

If it's a bug. I think it may due to Sun's light weight http server.

I did a very simple experiment:

1. Create a simple web method:


// Calculator.java
package simplews;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;

@WebService
public class Calculator {

@WebMethod
public int add(int a, int b) {
return a + b;
}
}





2. Generate web service class by using apt:

D:\>c:\Program Files\java\jdk1.6.0_06\bin"\apt -d . src\simplews\Calculator.java
warning: Annotation types without processors: [javax.xml.bind.annotation.XmlRootElement, javax.xml.bind.annotation.XmlAccessorType, javax.xml.bind.annotation.XmlType, javax.xml.bind.annotation.XmlElement]
1 warning


3. Create my endpoint:


//Main.java
package simplews;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.xml.ws.Endpoint;

public class Main {
private static ArrayBlockingQueue m_arrayBlockingQueue = null;
private static ThreadPoolExecutor m_executor = null;

public static void main(String[] args) {
// create and publish an endpoint
m_arrayBlockingQueue = new ArrayBlockingQueue(10000);
m_executor = new ThreadPoolExecutor(5, 50, 15L, TimeUnit.SECONDS, m_arrayBlockingQueue);
Calculator calculator = new Calculator();
Endpoint endpoint = Endpoint.publish("http://localhost:8080/calculator", calculator);
}
}


4. Create my web service client:


//Main.java
public class Main {

private static int totalThread = 20;
private static int countPerThread = 1000;
private static long sleeptime = 1L;

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
try {
totalThread = Integer.parseInt(args[0]);
} catch (Exception e) {
}
try {
countPerThread = Integer.parseInt(args[1]);
} catch (Exception e) {
}
try {
sleeptime = Long.parseLong(args[2]);
} catch (Exception e) {
}
for (int i = 0; i < totalThread; i++) {
Runnable runnable = new Runnable() {
int count = countPerThread;
public void run() {
while (--count > 0) {
try { // Call Web Service Operation
simplews.CalculatorService service = new simplews.CalculatorService();
simplews.Calculator port = service.getCalculatorPort();
// TODO initialize WS operation arguments here
int arg0 = 5;
int arg1 = 6;
// TODO process result here
int result = port.add(arg0, arg1);
System.out.println(Thread.currentThread().toString() + " | Result = " + result);
Thread.sleep(sleeptime);
} catch (Exception ex) {
// TODO handle custom exceptions here
ex.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();

try {
Thread.sleep(sleeptime);
} catch (Exception e) {
}
}
}
}




What I am doing here is to create certain number of threads and keep calling remote web method.


5. Start server

6. Start clients with 10 threads, 100 web service calls per thread and sleep = 1 millis.

Server side will keep throwing:


com.sun.xml.internal.ws.streaming.XMLStreamReaderException: XML reader error: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,2]
Message: The markup in the document preceding the root element must be well-formed.
at com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil.wrapException(XMLStreamReaderUtil.java:242)
at com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil.next(XMLStreamReaderUtil.java:70)
at com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil.nextContent(XMLStreamReaderUtil.java:85)
at com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil.nextElementContent(XMLStreamReaderUtil.java:75)
at com.sun.xml.internal.ws.encoding.StreamSOAPCodec.decode(StreamSOAPCodec.java:167)
at com.sun.xml.internal.ws.encoding.StreamSOAPCodec.decode(StreamSOAPCodec.java:149)
at com.sun.xml.internal.ws.encoding.StreamSOAPCodec.decode(StreamSOAPCodec.java:121)
at com.sun.xml.internal.ws.encoding.SOAPBindingCodec.decode(SOAPBindingCodec.java:280)
at com.sun.xml.internal.ws.transport.http.HttpAdapter.decodePacket(HttpAdapter.java:207)
at com.sun.xml.internal.ws.transport.http.HttpAdapter.access$500(HttpAdapter.java:74)
at com.sun.xml.internal.ws.transport.http.HttpAdapter$HttpToolkit.handle(HttpAdapter.java:363)
at com.sun.xml.internal.ws.transport.http.HttpAdapter.handle(HttpAdapter.java:175)
at com.sun.xml.internal.ws.transport.http.server.WSHttpHandler.handleExchange(WSHttpHandler.java:100)
at com.sun.xml.internal.ws.transport.http.server.WSHttpHandler.handle(WSHttpHandler.java:77)
at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:65)
at sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:65)
at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:68)
at sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:552)
at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:65)
at sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:524)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:885)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
at java.lang.Thread.run(Thread.java:619)
Caused by: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,2]
Message: The markup in the document preceding the root element must be well-formed.
at com.sun.org.apache.xerces.internal.impl.XMLStreamReaderImpl.next(XMLStreamReaderImpl.java:588)
at com.sun.xml.internal.ws.util.xml.XMLStreamReaderFilter.next(XMLStreamReaderFilter.java:78)
at com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil.next(XMLStreamReaderUtil.java:51)
... 21 more







At the same time, number of loopback connections (by Selector) at server
side will be increased by one per exception! Those loopback connections cannot be released unless restarting application.

The application will halt when the number of leaked connection rearch
server limit (max filedispatcher).

Bug 6712353 is dispatched and pending for fix, I am not sure when the fix can be released.

So, if you plan to use standalone jax-ws, make sure the bug is fixed otherwise you are in trouble. Currently one of our application in production is suffered and we have to detect number of leaked connections and trigger an auto-restart of the application in order to free those leaked connections.

I attached my test code here (NetBeans Project):

http://kentsang77.googlepages.com/SimpleWS.rar
http://kentsang77.googlepages.com/SimpleWSClient.rar

jdk version:

java version "1.6.0_06"
Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
Java HotSpot(TM) 64-Bit Server VM (build 10.0-b22, mixed mode)

2008年7月1日 星期二

VirtualBox

Recently, I start to play virtualization with my new toy, Intel Q9450 box. While searching information from the net, I found VirtualBox.

Wow! It really an ultra light-weight virtualization software. I start to install some guest OS. FreeBSD, Centos and Ubuntu. Some experience I want to share:

1. It seems that VirtualBox(1.6.2) doesn't support 64bits guest OS. Even my VirtualBox is 64bits.
2. Adding lan bridge to your guest OS require manually group your lan interface to bridge.
3. Cloning guest OS require VirtualBox's cloning command line tool:

VBoxManage clonevdi centos5.2.vdi centos5.2-2.vid

This command is okay if you save your Virtual Disk File in default location (C:\Documents and Settings\yourid\.VirtualBox\VDI).

If you have save your virtual disk file in another place, you have to specify the full path (both input file name and output file name). E.g.

VBoxManage clonevdi d:\vbox\centos5.2.vdi d:\vbox\centos5.2-2.vid

Otherwise you will got an error saying that Could not access hard disk image.