RE: Strange APR/native behaviour
"Caldarale, Charles R" wrote: >> From: Mark Thomas [mailto:ma...@apache.org] >> Subject: Strange APR/native behaviour > >> The problem I am seeing is with a non-blocking write in the >> AprServletOutputStream [1]. > >> If a write returns EAGAIN (line 97) the socket is added to the poller >> for write notifications. The poller immediately returns the socket as >> ready for write. Another write is attempted that returns EAGAIN and >the >> code enters an infinite loop with no more data ever written to the >socket. > >> If I add a delay around line 46 that slows the rate of writing down, >> EAGAIN is never returned and the test completes successfully. > >Are you positive the EAGAIN path is being taken? Or could it be an >exception is being thrown (and swallowed), leaving result = 0, thereby >adding the socket to the poller at line 93 and returning zero? > > - Chuck Yes, I am positive. I was using a debugger and had a break point in the EAGAIN path. Mark - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
Re: Strange APR/native behaviour
On Tue, 2013-03-12 at 00:25 +, Mark Thomas wrote: > I have been tracking down a problem with the Autobahn test suite and > APR/native connector when using SSL (all is fine with non-SSL). > > In the test Autobahn sends a 16MB frame to the server which the server > echos back to the client. > > The problem I am seeing is with a non-blocking write in the > AprServletOutputStream [1]. > > If a write returns EAGAIN (line 97) the socket is added to the poller > for write notifications. The poller immediately returns the socket as > ready for write. Another write is attempted that returns EAGAIN and the > code enters an infinite loop with no more data ever written to the socket. > > If I add a delay around line 46 that slows the rate of writing down, > EAGAIN is never returned and the test completes successfully. > > I assume I am doing something wrong. Pointers appreciated. So I don't know precisely, but for non blocking, you're not doing the same thing I'm doing (which went through stress tests successfully on some platforms - but not on OS X, the OS is too cool for using it for this kind of dirty work, I presume). Here are some differences (that could be attempted): - to set non blocking, I don't use optSet with APR_SO_NONBLOCK (it did weird things at that time, not 100% sure now), but Socket.timeoutSet(socket, 0) since Mladen told me that is the proper way to set non blocking mode - to do the actual send of the bytes, I use sendibb (which is sendib with an internal byte buffer), I think it's not the same as the regular send since it doesn't retry (says the javadoc, not sure if it can be fully trusted ;) ) - I check if it needs to be put in the poller using the number of bytes wrtten (if 0, it goes to the poller, if not, write again if there's more data), not some status like EAGAIN (IMO that's riskier, since the exact bahavior of statuses are sometimes platform specific) - to exit non blocking mode, I set the previous timeout value and use sendbb (again, with the internal byte buffer) - I don't use syncs on IO, the design of the async in Servlet 3.1 is supposed to allow avoiding that (of course, if the user does concurrent writes without a lock on his own, it will bomb, but IMO, it should) Rémy - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
Re: Strange APR/native behaviour
On 12/03/2013 09:47, Remy Maucherat wrote: > On Tue, 2013-03-12 at 00:25 +, Mark Thomas wrote: >> I assume I am doing something wrong. Pointers appreciated. > > So I don't know precisely, but for non blocking, you're not doing the > same thing I'm doing (which went through stress tests successfully on > some platforms - but not on OS X, the OS is too cool for using it for > this kind of dirty work, I presume). > > Here are some differences (that could be attempted): > - to set non blocking, I don't use optSet with APR_SO_NONBLOCK (it did > weird things at that time, not 100% sure now), but > Socket.timeoutSet(socket, 0) since Mladen told me that is the proper way > to set non blocking mode > - to do the actual send of the bytes, I use sendibb (which is sendib > with an internal byte buffer), I think it's not the same as the regular > send since it doesn't retry (says the javadoc, not sure if it can be > fully trusted ;) ) > - I check if it needs to be put in the poller using the number of bytes > wrtten (if 0, it goes to the poller, if not, write again if there's more > data), not some status like EAGAIN (IMO that's riskier, since the exact > bahavior of statuses are sometimes platform specific) > - to exit non blocking mode, I set the previous timeout value and use > sendbb (again, with the internal byte buffer) > - I don't use syncs on IO, the design of the async in Servlet 3.1 is > supposed to allow avoiding that (of course, if the user does concurrent > writes without a lock on his own, it will bomb, but IMO, it should) Thanks for the pointers. I tried a few of them (the less invasive ones) with no luck. I'm putting together a test case that demonstrates the problem. That should make it very simple to determine if the root cause lies in APR or my code. Mark - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
Re: Strange APR/native behaviour
On Tue, 2013-03-12 at 13:25 +, Mark Thomas wrote: > Thanks for the pointers. I tried a few of them (the less invasive ones) > with no luck. I'm putting together a test case that demonstrates the > problem. That should make it very simple to determine if the root cause > lies in APR or my code. Ok, too bad. I was thinking the timeout trick could do it since the "proper" non blocking option + EAGAIN didn't work that well for me then. But that was a while ago. Using a 0 timeout is simpler overall, but obviously it looks like a hack. Yes, if you can write a simple test case, it would probably be much easier to isolate an issue in APR, it's likely far too complex otherwise. Rémy - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
svn commit: r1455688 - in /tomcat/trunk/test/org/apache/tomcat/jni: ./ TestSocket.java
Author: markt Date: Tue Mar 12 20:01:26 2013 New Revision: 1455688 URL: http://svn.apache.org/r1455688 Log: Test case that attempts to replicate the problem I am seeing with WebSocket and APR. The test case currently passes. The major differences are that the test case doesn't use a poller and doesn't use SSL. I'll add those next. Added: tomcat/trunk/test/org/apache/tomcat/jni/ tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java (with props) Added: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java?rev=1455688&view=auto == --- tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java (added) +++ tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Tue Mar 12 20:01:26 2013 @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.tomcat.jni; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +import org.junit.Assert; +import org.junit.Test; + +public class TestSocket { + +private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); +private static final Object clientWait = new Object(); +private static final Object serverWait = new Object(); + +@Test +public void testNonBlockingEagain() throws Exception { + +try { +Library.initialize(null); +} catch (Throwable t) { +System.err.println("Unable to initialize APR library"); +t.printStackTrace(System.err); +return; +} + +Server s = new Server(); + +int port = s.getPort(); + +// Start the server +java.lang.Thread t = new java.lang.Thread(s); +t.setName("testNonBlockingEagain-Server"); +t.start(); + +// Open a connection to the server +String host = null; +java.net.Socket socket = new java.net.Socket(host, port); +// Infinite timeout +socket.setSoTimeout(0); +InputStream is = socket.getInputStream(); + +ByteBuffer bb = ByteBuffer.allocate(20); +int data = 0; + + +// Read the first 20 digits. +data = clientRead(is, bb, data, 20); + +// Block until the server fills up it's send buffers +/* +synchronized (clientWait) { +clientWait.wait(); +} +*/ + +// Read the next 1000 digits +data = clientRead(is, bb, data, 1000); + +// Unblock the server +synchronized (serverWait) { +serverWait.notify(); +} + +// Read to the end +clientRead(is, bb, data, 10); + +socket.close(); +} + + +private int clientRead(InputStream is, ByteBuffer bb, int data, int limit) +throws IOException { +while (data < limit) { +byte[] readBuffer = new byte[10]; +byte[] dataBuffer = new byte[10]; +int read = is.read(readBuffer); +if (read == -1) { +Assert.fail(); +} +bb.put(readBuffer, 0, read); + +String expected = Integer.toString(data); +// Short-cut In ISO-8859-1 1 char uses 1 byte +int len = expected.length(); +while (bb.position() >= len) { +bb.flip(); +bb.get(dataBuffer, 0, len); +String actual = new String(dataBuffer, 0, len, ISO_8859_1); +Assert.assertEquals(expected, actual); +data++; +expected = Integer.toString(data); +len = expected.length(); +bb.compact(); +} +} +return data; +} + + +private static final class Server implements Runnable { + +private final long rootPool; +private final long serverSock; + +public Server() throws Exception { +rootPool = Pool.create(0); + +// Server socket +/*long serverSockPool = */ Pool.create(rootPool); +long inetAddress = +Address.inf
svn commit: r1455689 - /tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java
Author: markt Date: Tue Mar 12 20:02:54 2013 New Revision: 1455689 URL: http://svn.apache.org/r1455689 Log: Comment in/out the right bits. Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java?rev=1455689&r1=1455688&r2=1455689&view=diff == --- tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java (original) +++ tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Tue Mar 12 20:02:54 2013 @@ -65,11 +65,9 @@ public class TestSocket { data = clientRead(is, bb, data, 20); // Block until the server fills up it's send buffers -/* synchronized (clientWait) { clientWait.wait(); } -*/ // Read the next 1000 digits data = clientRead(is, bb, data, 1000); @@ -175,7 +173,7 @@ public class TestSocket { written = Socket.send(socket, b, start, len); if (written < 0) { if (Status.APR_STATUS_IS_EAGAIN(-written)) { -System.out.println("EAGAIN"); +// System.out.println("EAGAIN"); written = 0; } else { System.out.println("Error code [" + -written + "]"); - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
[Tomcat Wiki] Update of "Tomcat/UTF-8" by Sergio Soto
Dear Wiki user, You have subscribed to a wiki page or wiki category on "Tomcat Wiki" for change notification. The "Tomcat/UTF-8" page has been changed by Sergio Soto: http://wiki.apache.org/tomcat/Tomcat/UTF-8?action=diff&rev1=16&rev2=18 - This page is obsolete. See [[FAQ/CharacterEncoding|FAQ/CharacterEncoding]] for the up-to-date version. + Describe Tomcat/UTF-8 here. - - CategoryObsolete - - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
svn commit: r1455700 - /tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java
Author: markt Date: Tue Mar 12 20:35:26 2013 New Revision: 1455700 URL: http://svn.apache.org/r1455700 Log: Add poller to test (still passes) Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java?rev=1455700&r1=1455699&r2=1455700&view=diff == --- tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java (original) +++ tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Tue Mar 12 20:35:26 2013 @@ -27,8 +27,6 @@ import org.junit.Test; public class TestSocket { private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); -private static final Object clientWait = new Object(); -private static final Object serverWait = new Object(); @Test public void testNonBlockingEagain() throws Exception { @@ -64,17 +62,27 @@ public class TestSocket { // Read the first 20 digits. data = clientRead(is, bb, data, 20); -// Block until the server fills up it's send buffers -synchronized (clientWait) { -clientWait.wait(); +// Block until the server fills up it's send buffers and then sleep for +// 5 seconds +int count = 0; +while (count < 5) { +java.lang.Thread.sleep(1000); +if (s.isPolling()) { +count ++; +} } -// Read the next 1000 digits -data = clientRead(is, bb, data, 1000); +// Read the next 1 digits +data = clientRead(is, bb, data, 1); -// Unblock the server -synchronized (serverWait) { -serverWait.notify(); +// Block until the server fills up it's send buffers and then sleep for +// 5 seconds +count = 0; +while (count < 5) { +java.lang.Thread.sleep(1000); +if (s.isPolling()) { +count ++; +} } // Read to the end @@ -116,7 +124,12 @@ public class TestSocket { private static final class Server implements Runnable { private final long rootPool; -private final long serverSock; +private final long serverSocket; +private final long serverSocketPool; +private final long pollerPool; +private final long poller; + +private volatile boolean polling = false; public Server() throws Exception { rootPool = Pool.create(0); @@ -125,41 +138,48 @@ public class TestSocket { /*long serverSockPool = */ Pool.create(rootPool); long inetAddress = Address.info(null, Socket.APR_INET, 0, 0, rootPool); -serverSock = Socket.create( +serverSocket = Socket.create( Address.getInfo(inetAddress).family, Socket.SOCK_STREAM, Socket.APR_PROTO_TCP, rootPool); -int ret = Socket.bind(serverSock, inetAddress); +int ret = Socket.bind(serverSocket, inetAddress); if (ret != 0) { throw new IOException("Bind failed [" + ret + "]"); } -ret = Socket.listen(serverSock, 100); +ret = Socket.listen(serverSocket, 100); if (ret != 0) { throw new IOException("Listen failed [" + ret + "]"); } + +// Poller +serverSocketPool = Pool.create(rootPool); +pollerPool = Pool.create(serverSocketPool); +poller = Poll.create(10, pollerPool, 0, -1); } public int getPort() throws Exception { -long sa = Address.get(Socket.APR_LOCAL, serverSock); +long sa = Address.get(Socket.APR_LOCAL, serverSocket); Sockaddr addr = Address.getInfo(sa); return addr.port; } +public boolean isPolling() { +return polling; +} + @Override public void run() { try { // Accept an incoming connection -long socket = Socket.accept(serverSock); +long socket = Socket.accept(serverSocket); // Make socket non-blocking Socket.timeoutSet(socket, 0); -boolean first = true; - int data = 0; do { String s = Integer.toString(data); @@ -185,18 +205,29 @@ public class TestSocket { len -= written; if (written == 0 && len > 0) { -if (first) { -first = false; -// Stop client from blocking to free up some -// space so the server can write -
Re: [VOTE] Release Apache Tomcat 7.0.38
On 07/03/2013 22:47, Mark Thomas wrote: > The proposed Apache Tomcat 7.0.38 release is now available for voting. > > It can be obtained from: > https://dist.apache.org/repos/dist/dev/tomcat/tomcat-7/v7.0.38/ > The Maven staging repo is: > https://repository.apache.org/content/repositories/orgapachetomcat-002/ > The svn tag is: > http://svn.apache.org/repos/asf/tomcat/tc7.0.x/tags/TOMCAT_7_0_38/ > > The proposed 7.0.38 release is: > [ ] Broken - do not release > [X] Stable - go ahead and release as 7.0.38 Stable Unit tests pass on 64-bit Windows and 64-bit Linux EL TCK passes JSP TCK passes with HTTP (direct) BIO, NIO & APR/native (1.1.27) Servlet TCK passes with - HTTP (direct) BIO, NIO & APR/native (1.1.27) - HTTP (mod_proxy_http) BIO, NIO & APR/native (1.1.27) - AJP (mod_jk) BIO, NIO & APR/native (1.1.27) - AJP (mod_proxy_ajp) BIO, NIO & APR/native (1.1.27) All TCK tests run on 64-bit Linux with a security manager Mark - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
svn commit: r1455714 - /tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java
Author: markt Date: Tue Mar 12 21:08:28 2013 New Revision: 1455714 URL: http://svn.apache.org/r1455714 Log: Add SSL. Test still passes. Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java?rev=1455714&r1=1455713&r2=1455714&view=diff == --- tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java (original) +++ tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Tue Mar 12 21:08:28 2013 @@ -16,14 +16,21 @@ */ package org.apache.tomcat.jni; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.security.SecureRandom; + +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; import org.junit.Assert; import org.junit.Test; +import org.apache.tomcat.util.net.TesterSupport; + public class TestSocket { private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); @@ -50,7 +57,13 @@ public class TestSocket { // Open a connection to the server String host = null; -java.net.Socket socket = new java.net.Socket(host, port); +javax.net.ssl.SSLContext sc = +javax.net.ssl.SSLContext.getInstance("SSL"); +sc.init(null, new TrustManager[] {new TesterSupport.TrustAllCerts()} , +new SecureRandom()); +SSLSocketFactory factory = sc.getSocketFactory(); +java.net.Socket socket = factory.createSocket(host, port); + // Infinite timeout socket.setSoTimeout(0); InputStream is = socket.getInputStream(); @@ -126,6 +139,7 @@ public class TestSocket { private final long rootPool; private final long serverSocket; private final long serverSocketPool; +private final long sslContext; private final long pollerPool; private final long poller; @@ -153,6 +167,20 @@ public class TestSocket { throw new IOException("Listen failed [" + ret + "]"); } +// Setup SSL +SSL.randSet("builtin"); +SSL.initialize(null); +sslContext = SSLContext.make( +rootPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); +SSLContext.setCipherSuite(sslContext, "ALL"); +File certFile = new File( +"test/org/apache/tomcat/util/net/localhost-cert.pem"); +File keyFile = new File( +"test/org/apache/tomcat/util/net/localhost-key.pem"); +SSLContext.setCertificate(sslContext, certFile.getAbsolutePath(), +keyFile.getAbsolutePath(), null, SSL.SSL_AIDX_RSA); +SSLContext.setVerify(sslContext, SSL.SSL_CVERIFY_NONE, 10); + // Poller serverSocketPool = Pool.create(rootPool); pollerPool = Pool.create(serverSocketPool); @@ -177,9 +205,16 @@ public class TestSocket { // Accept an incoming connection long socket = Socket.accept(serverSocket); +// Configure SSL +SSLSocket.attach(sslContext, socket); +if (SSLSocket.handshake(socket) != 0) { +System.err.println("SSL handshake failed"); +} + // Make socket non-blocking Socket.timeoutSet(socket, 0); + int data = 0; do { String s = Integer.toString(data); - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
svn commit: r1455737 - /tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java
Author: markt Date: Tue Mar 12 22:35:06 2013 New Revision: 1455737 URL: http://svn.apache.org/r1455737 Log: Make SSL optional Try to write data in one large chunk. Unexpected values for written are now observable. Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java?rev=1455737&r1=1455736&r2=1455737&view=diff == --- tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java (original) +++ tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Tue Mar 12 22:35:06 2013 @@ -23,7 +23,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.security.SecureRandom; -import javax.net.ssl.SSLSocketFactory; +import javax.net.SocketFactory; import javax.net.ssl.TrustManager; import org.junit.Assert; @@ -35,6 +35,29 @@ public class TestSocket { private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); +// 1,000Writes all the data in the first write +// 100,000 Shows strange return values for written +// 10,000,000 Shows strange return values for written more clearly +private static final int LIMIT = 10; +private static final byte[] DATA; +private static final boolean ENABLE_SSL = true; + +static { +int pos = 0; +int size = 0; +for (int i = 0; i < LIMIT; i++) { +String s = Integer.toString(i); +size += s.length(); +} +DATA = new byte[size]; +for (int i = 0; i < LIMIT; i++) { +String s = Integer.toString(i); +byte[] b = s.getBytes(ISO_8859_1); +System.arraycopy(b, 0, DATA, pos, b.length); +pos += b.length; +} +} + @Test public void testNonBlockingEagain() throws Exception { @@ -57,11 +80,16 @@ public class TestSocket { // Open a connection to the server String host = null; -javax.net.ssl.SSLContext sc = -javax.net.ssl.SSLContext.getInstance("SSL"); -sc.init(null, new TrustManager[] {new TesterSupport.TrustAllCerts()} , -new SecureRandom()); -SSLSocketFactory factory = sc.getSocketFactory(); +SocketFactory factory; +if (ENABLE_SSL) { +javax.net.ssl.SSLContext sc = +javax.net.ssl.SSLContext.getInstance("SSL"); +sc.init(null, new TrustManager[] {new TesterSupport.TrustAllCerts()} , +new SecureRandom()); + factory = sc.getSocketFactory(); +} else { +factory = SocketFactory.getDefault(); +} java.net.Socket socket = factory.createSocket(host, port); // Infinite timeout @@ -74,24 +102,26 @@ public class TestSocket { // Read the first 20 digits. data = clientRead(is, bb, data, 20); +System.out.println("Client read first 20 digits"); // Block until the server fills up it's send buffers and then sleep for // 5 seconds int count = 0; -while (count < 5) { +while (count < 3) { java.lang.Thread.sleep(1000); if (s.isPolling()) { count ++; } } -// Read the next 1 digits -data = clientRead(is, bb, data, 1); +// Read 30% of the data +data = clientRead(is, bb, data, (int) (LIMIT * 0.3)); +System.out.println("Client read first 30% of digits"); // Block until the server fills up it's send buffers and then sleep for // 5 seconds count = 0; -while (count < 5) { +while (count < 3) { java.lang.Thread.sleep(1000); if (s.isPolling()) { count ++; @@ -99,7 +129,8 @@ public class TestSocket { } // Read to the end -clientRead(is, bb, data, 10); +clientRead(is, bb, data, LIMIT); +System.out.println("Client read all digits"); socket.close(); } @@ -128,6 +159,9 @@ public class TestSocket { expected = Integer.toString(data); len = expected.length(); bb.compact(); +if (data%1000 == 0) { +System.out.println("Client read to [" + data + "]"); +} } } return data; @@ -168,18 +202,22 @@ public class TestSocket { } // Setup SSL -SSL.randSet("builtin"); -SSL.initialize(null); -sslContext = SSLContext.make( -rootPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); -SSLContext.setCipherSuite(sslContext, "ALL"); -File certFile = new File( -"test/org/apache/tomcat/util/net/localh
svn commit: r1455763 - /tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java
Author: markt Date: Wed Mar 13 00:09:32 2013 New Revision: 1455763 URL: http://svn.apache.org/r1455763 Log: Problem now reproducible (need to drop chucksize to 2) Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Modified: tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java?rev=1455763&r1=1455762&r2=1455763&view=diff == --- tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java (original) +++ tomcat/trunk/test/org/apache/tomcat/jni/TestSocket.java Wed Mar 13 00:09:32 2013 @@ -40,6 +40,8 @@ public class TestSocket { // 10,000,000 Shows strange return values for written more clearly private static final int LIMIT = 10; private static final byte[] DATA; +// Drop the chunk size to 2 and the problem occurs more often than not. +private static final int CHUNK_SIZE = 8; private static final boolean ENABLE_SSL = true; static { @@ -107,7 +109,7 @@ public class TestSocket { // Block until the server fills up it's send buffers and then sleep for // 5 seconds int count = 0; -while (count < 3) { +while (count < 5) { java.lang.Thread.sleep(1000); if (s.isPolling()) { count ++; @@ -121,7 +123,7 @@ public class TestSocket { // Block until the server fills up it's send buffers and then sleep for // 5 seconds count = 0; -while (count < 3) { +while (count < 5) { java.lang.Thread.sleep(1000); if (s.isPolling()) { count ++; @@ -137,7 +139,7 @@ public class TestSocket { private int clientRead(InputStream is, ByteBuffer bb, int data, int limit) -throws IOException { +throws Exception { while (data < limit) { byte[] readBuffer = new byte[10]; byte[] dataBuffer = new byte[10]; @@ -160,7 +162,7 @@ public class TestSocket { len = expected.length(); bb.compact(); if (data%1000 == 0) { -System.out.println("Client read to [" + data + "]"); +System.out.println("Client read to [" + data + "]"); } } } @@ -254,65 +256,88 @@ public class TestSocket { // Make socket non-blocking Socket.timeoutSet(socket, 0); -int start = 0; -int left = DATA.length; -int written; +int dataStart = 0; +int dataLeft = DATA.length; -System.out.println("To write [" + left + "]"); +byte[] chunk = new byte[CHUNK_SIZE]; +int chunkStart; +int chunkLeft; +int chunkWritten; + + + +System.out.println("Data write [" + dataLeft + "]"); do { -written = Socket.send(socket, DATA, start, left); -if (written < 0) { -if (Status.APR_STATUS_IS_EAGAIN(-written)) { -System.out.println("EAGAIN"); -written = 0; -} else { -System.out.println("Error code [" + -written + "]"); -throw new RuntimeException(); -} +chunkStart = 0; +if (dataLeft < CHUNK_SIZE) { +chunkLeft = dataLeft; +} else { +chunkLeft = CHUNK_SIZE; } -start += written; -left -= written; -System.out.println( -"Written: [" +written + "], Left [" + left + "]"); - -if (written == 0 && left > 0) { -// Write buffer is full. Poll until there is space -Poll.add(poller, socket, Poll.APR_POLLOUT); -polling = true; -int pollCount = 0; - -int rv = 0; -long[] desc = new long[2]; - -while (rv == 0) { -System.out.println("Poll. Left [" + left + "]"); -rv = Poll.poll(poller, 100, desc, true); -pollCount++; - -if (rv > 0) { -// There is space. Continue to write. -} else if (-rv == Status.TIMEUP) { -rv = 0; -// Poll timed out. Poll again. -} else if (rv < 0) { -// Something went wrong -
Re: Strange APR/native behaviour
On 12/03/2013 18:25, Remy Maucherat wrote: > On Tue, 2013-03-12 at 13:25 +, Mark Thomas wrote: >> Thanks for the pointers. I tried a few of them (the less invasive ones) >> with no luck. I'm putting together a test case that demonstrates the >> problem. That should make it very simple to determine if the root cause >> lies in APR or my code. > > Ok, too bad. I was thinking the timeout trick could do it since the > "proper" non blocking option + EAGAIN didn't work that well for me then. > But that was a while ago. Using a 0 timeout is simpler overall, but > obviously it looks like a hack. > > Yes, if you can write a simple test case, it would probably be much > easier to isolate an issue in APR, it's likely far too complex > otherwise. And we have a test case. Run the latest TestSocket with: LIMIT=10 SSL_ENABLED=true CHUNK_SIZE=2 The problem isn't 100% reproducible but: - I have never been able to repeat it with SSL disabled - I saw the problem once with a chunk size of 4 - I have never seen the problem with a chunk size > 4 Based on what this test does any my observations I have a theory but it will need someone who understands the APR/native code better than I do to confirm the theory and (if correct) produce a fix. I am assuming the the SSL encryption processes the data in chunks where the chunk size is a small number of bytes (e.g. 8). What I think happens is this: Lots of data is written to the socket. The write buffers become 99.9% full. The client writes a very small amount of data (e.g. 4 bytes). This is enough for the SSL encryption to output a little more data so the write buffers are now 100% full. Not all of the 4 bytes from the client were read. Socket gets added to the Poller. Other end of the socket reads some data. Poller fires write possible. StartLoop: Client writes remaining bytes (<4). There is not enough new input for the SSL encryption to generate more output so it reports 0 bytes written. Client believes write buffers are still full. Socket gets added to the Poller. Poller instantly fires write possible Goto StartLoop And an infinite loop is entered. I believe I can fix this by changing how I do the buffering in WebSocket so I don't end up having to write short 4 byte chunks. I have been meaning to do this for a while anyway. That said, this does look like an issue with APR/native and SSL. Thoughts? Mark - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org