/* * * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) * * * * 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. * * * * For more information: http://www.orientechnologies.com * */ package com.orientechnologies.orient.client.binary; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import com.orientechnologies.common.concur.OTimeoutException; import com.orientechnologies.common.concur.lock.OInterruptedException; import com.orientechnologies.common.concur.lock.OLockException; import com.orientechnologies.common.exception.OException; import com.orientechnologies.common.exception.OSystemException; import com.orientechnologies.common.io.OIOException; import com.orientechnologies.common.log.OLogManager; import com.orientechnologies.common.util.OPair; import com.orientechnologies.orient.client.remote.OStorageRemoteNodeSession; import com.orientechnologies.orient.client.remote.OStorageRemoteSession; import com.orientechnologies.orient.core.config.OContextConfiguration; import com.orientechnologies.orient.core.config.OGlobalConfiguration; import com.orientechnologies.orient.core.serialization.OMemoryInputStream; import com.orientechnologies.orient.enterprise.channel.OSocketFactory; import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinary; import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; import com.orientechnologies.orient.enterprise.channel.binary.ONetworkProtocolException; import com.orientechnologies.orient.enterprise.channel.binary.ORemoteServerEventListener; import com.orientechnologies.orient.enterprise.channel.binary.OResponseProcessingException; public class OChannelBinaryAsynchClient extends OChannelBinary { private int socketTimeout; // IN MS protected final short srvProtocolVersion; private final Condition readCondition = getLockRead().getUnderlying().newCondition(); private final int maxUnreadResponses; private String serverURL; private volatile boolean channelRead = false; private byte currentStatus; private int currentSessionId; private volatile OAsynchChannelServiceThread serviceThread; public OChannelBinaryAsynchClient(final String remoteHost, final int remotePort, final String iDatabaseName, final OContextConfiguration iConfig, final int iProtocolVersion) throws IOException { this(remoteHost, remotePort, iDatabaseName, iConfig, iProtocolVersion, null); } public OChannelBinaryAsynchClient(final String remoteHost, final int remotePort, final String iDatabaseName, final OContextConfiguration iConfig, final int protocolVersion, final ORemoteServerEventListener asynchEventListener) throws IOException { super(OSocketFactory.instance(iConfig).createSocket(), iConfig); try { maxUnreadResponses = OGlobalConfiguration.NETWORK_BINARY_READ_RESPONSE_MAX_TIMES.getValueAsInteger(); serverURL = remoteHost + ":" + remotePort; if (iDatabaseName != null) serverURL += "/" + iDatabaseName; socketTimeout = iConfig.getValueAsInteger(OGlobalConfiguration.NETWORK_SOCKET_TIMEOUT); try { socket.connect(new InetSocketAddress(remoteHost, remotePort), getSocketTimeout()); setReadResponseTimeout(); connected(); } catch (java.net.SocketTimeoutException e) { throw new IOException("Cannot connect to host " + remoteHost + ":" + remotePort, e); } try { if (socketBufferSize > 0) { inStream = new BufferedInputStream(socket.getInputStream(), socketBufferSize); outStream = new BufferedOutputStream(socket.getOutputStream(), socketBufferSize); } else { inStream = new BufferedInputStream(socket.getInputStream()); outStream = new BufferedOutputStream(socket.getOutputStream()); } in = new DataInputStream(inStream); out = new DataOutputStream(outStream); srvProtocolVersion = readShort(); } catch (IOException e) { throw new ONetworkProtocolException( "Cannot read protocol version from remote server " + socket.getRemoteSocketAddress() + ": " + e); } if (srvProtocolVersion != protocolVersion) { OLogManager.instance().warn(this, "The Client driver version is different than Server version: client=" + protocolVersion + ", server=" + srvProtocolVersion + ". You could not use the full features of the newer version. Assure to have the same versions on both"); } if (asynchEventListener != null) serviceThread = new OAsynchChannelServiceThread(asynchEventListener, this); } catch (RuntimeException e) { if (socket.isConnected()) socket.close(); throw e; } } @SuppressWarnings("unchecked") private static RuntimeException createException(final String iClassName, final String iMessage, final Exception iPrevious) { RuntimeException rootException = null; Constructor<?> c = null; try { final Class<RuntimeException> excClass = (Class<RuntimeException>) Class.forName(iClassName); if (iPrevious != null) { try { c = excClass.getConstructor(String.class, Throwable.class); } catch (NoSuchMethodException e) { c = excClass.getConstructor(String.class, Exception.class); } } if (c == null) c = excClass.getConstructor(String.class); } catch (Exception e) { // UNABLE TO REPRODUCE THE SAME SERVER-SIDE EXCEPTION: THROW AN SYSTEM EXCEPTION rootException = OException.wrapException(new OSystemException(iMessage), iPrevious); } if (c != null) try { final Exception cause; if (c.getParameterTypes().length > 1) cause = (Exception) c.newInstance(iMessage, iPrevious); else cause = (Exception) c.newInstance(iMessage); rootException = OException.wrapException(new OSystemException("Data processing exception"), cause); } catch (InstantiationException ignored) { } catch (IllegalAccessException ignored) { } catch (InvocationTargetException ignored) { } return rootException; } public byte[] beginResponse(final int iRequesterId, final boolean token) throws IOException { return beginResponse(iRequesterId, timeout, token); } public byte[] beginResponse(final int iRequesterId, final long iTimeout, final boolean token) throws IOException { try { int unreadResponse = 0; final long startClock = iTimeout > 0 ? System.currentTimeMillis() : 0; // WAIT FOR THE RESPONSE do { if (iTimeout <= 0) acquireReadLock(); else if (!getLockRead().tryAcquireLock(iTimeout, TimeUnit.MILLISECONDS)) throw new OTimeoutException("Cannot acquire read lock against channel: " + this); boolean readLock = true; if (!isConnected()) { releaseReadLock(); throw new IOException("Channel is closed"); } if (!channelRead) { channelRead = true; try { setWaitResponseTimeout(); currentStatus = readByte(); currentSessionId = readInt(); if (debug) OLogManager.instance().debug(this, "%s - Read response: %d-%d", socket.getLocalAddress(), (int) currentStatus, currentSessionId); } catch (IOException e) { // UNLOCK THE RESOURCE AND PROPAGATES THE EXCEPTION channelRead = false; readCondition.signalAll(); releaseReadLock(); readLock = false; throw e; } finally { setReadResponseTimeout(); } } if (currentSessionId == iRequesterId) // IT'S FOR ME break; try { if (debug) OLogManager.instance().debug(this, "%s - Session %d skip response, it is for %d", socket.getLocalAddress(), iRequesterId, currentSessionId); if (iTimeout > 0 && (System.currentTimeMillis() - startClock) > iTimeout) { readLock = false; throw new IOException("Timeout on reading response from the server " + (socket != null ? socket.getRemoteSocketAddress() : "") + " for the request " + iRequesterId); } // IN CASE OF TOO MUCH TIME FOR READ A MESSAGE, ASYNC THREAD SHOULD NOT BE INCLUDE IN THIS CHECK if (unreadResponse > maxUnreadResponses && iRequesterId != Integer.MIN_VALUE) { if (debug) OLogManager.instance().info(this, "Unread responses %d > %d, consider the buffer as dirty: clean it", unreadResponse, maxUnreadResponses); readLock = false; throw new IOException("Timeout on reading response"); } readCondition.signalAll(); if (debug) OLogManager.instance().debug(this, "Session %d is going to sleep...", iRequesterId); final long start = System.currentTimeMillis(); // WAIT MAX 30 SECOND AND RETRY, THIS IS UNBLOCKED BY ANOTHER THREAD IN CASE THE RESPONSE FOR THIS IS ARRIVED readCondition.await(30, TimeUnit.SECONDS); if (debug) { final long now = System.currentTimeMillis(); OLogManager.instance().debug(this, "Waked up: slept %dms, checking again from %s for session %d", (now - start), socket.getLocalAddress(), iRequesterId); } unreadResponse++; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw OException.wrapException(new OInterruptedException("Thread interrupted while waiting for request"), e); } finally { if (readLock) releaseReadLock(); } } while (true); if (debug) OLogManager.instance().debug(this, "%s - Session %d handle response", socket.getLocalAddress(), iRequesterId); byte[] tokenBytes; if (token) tokenBytes = this.readBytes(); else tokenBytes = null; handleStatus(currentStatus, currentSessionId); return tokenBytes; } catch (OLockException e) { Thread.currentThread().interrupt(); // NEVER HAPPENS? OLogManager.instance().error(this, "Unexpected error on reading response from channel", e); } return null; } public void endResponse() throws IOException { channelRead = false; // WAKE UP ALL THE WAITING THREADS try { readCondition.signalAll(); } catch (IllegalMonitorStateException e) { // IGNORE IT OLogManager.instance().debug(this, "Error on signaling waiting clients after reading response"); } try { releaseReadLock(); } catch (IllegalMonitorStateException e) { // IGNORE IT OLogManager.instance().debug(this, "Error on unlocking network channel after reading response"); } } @Override public void close() { if (getLockRead().tryAcquireLock()) try { readCondition.signalAll(); } finally { releaseReadLock(); } try { super.close(); } catch (Exception e) { // IGNORE IT } if (serviceThread != null) { final OAsynchChannelServiceThread s = serviceThread; serviceThread = null; if (s != null) // CHECK S BECAUSE IT COULD BE CONCURRENTLY RESET s.sendShutdown(); } } @Override public void clearInput() throws IOException { acquireReadLock(); try { super.clearInput(); } finally { releaseReadLock(); } } /** * Tells if the channel is connected. * * @return true if it's connected, otherwise false. */ public boolean isConnected() { final Socket s = socket; return s != null && !s.isClosed() && s.isConnected() && !s.isInputShutdown() && !s.isOutputShutdown(); } /** * Gets the major supported protocol version * */ public short getSrvProtocolVersion() { return srvProtocolVersion; } public String getServerURL() { return serverURL; } public boolean tryLock() { return getLockWrite().tryAcquireLock(); } public void unlock() { getLockWrite().unlock(); } public OAsynchChannelServiceThread getServiceThread() { return serviceThread; } protected int handleStatus(final byte iResult, final int iClientTxId) throws IOException { if (iResult == OChannelBinaryProtocol.RESPONSE_STATUS_OK || iResult == OChannelBinaryProtocol.PUSH_DATA) { return iClientTxId; } else if (iResult == OChannelBinaryProtocol.RESPONSE_STATUS_ERROR) { final List<OPair<String, String>> exceptions = new ArrayList<OPair<String, String>>(); // EXCEPTION while (readByte() == 1) { final String excClassName = readString(); final String excMessage = readString(); exceptions.add(new OPair<String, String>(excClassName, excMessage)); } byte[] serializedException = null; if (srvProtocolVersion >= 19) serializedException = readBytes(); Exception previous = null; if (serializedException != null && serializedException.length > 0) throwSerializedException(serializedException); for (int i = exceptions.size() - 1; i > -1; --i) { previous = createException(exceptions.get(i).getKey(), exceptions.get(i).getValue(), previous); } if (previous != null) { throw new RuntimeException(previous); } else throw new ONetworkProtocolException("Network response error"); } else { // PROTOCOL ERROR // close(); throw new ONetworkProtocolException("Error on reading response from the server"); } } private void setReadResponseTimeout() throws SocketException { final Socket s = socket; if (s != null && s.isConnected() && !s.isClosed()) s.setSoTimeout(getSocketTimeout()); } private void setWaitResponseTimeout() throws SocketException { final Socket s = socket; if (s != null) s.setSoTimeout(OGlobalConfiguration.NETWORK_REQUEST_TIMEOUT.getValueAsInteger()); } private void throwSerializedException(final byte[] serializedException) throws IOException { final OMemoryInputStream inputStream = new OMemoryInputStream(serializedException); final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); Object throwable = null; try { throwable = objectInputStream.readObject(); } catch (ClassNotFoundException e) { OLogManager.instance().error(this, "Error during exception deserialization", e); throw new IOException("Error during exception deserialization: " + e.toString()); } objectInputStream.close(); if (throwable instanceof OException) { try { final Class<? extends OException> cls = (Class<? extends OException>) throwable.getClass(); final Constructor<? extends OException> constructor; constructor = cls.getConstructor(cls); final OException proxyInstance = constructor.newInstance(throwable); throw proxyInstance; } catch (NoSuchMethodException e) { OLogManager.instance().error(this, "Error during exception deserialization", e); } catch (InvocationTargetException e) { OLogManager.instance().error(this, "Error during exception deserialization", e); } catch (InstantiationException e) { OLogManager.instance().error(this, "Error during exception deserialization", e); } catch (IllegalAccessException e) { OLogManager.instance().error(this, "Error during exception deserialization", e); } } if (throwable instanceof Throwable) { throw new OResponseProcessingException("Exception during response processing", (Throwable) throwable); } // WRAP IT else OLogManager.instance().error(this, "Error during exception serialization, serialized exception is not Throwable, exception type is " + (throwable != null ? throwable.getClass().getName() : "null")); } public void beginRequest(final byte iCommand, final OStorageRemoteSession session) throws IOException { final OStorageRemoteNodeSession nodeSession = session.getServerSession(getServerURL()); if (nodeSession == null) throw new OIOException("Invalid session for URL '" + getServerURL() + "'"); writeByte(iCommand); writeInt(nodeSession.getSessionId()); if (nodeSession.getToken() != null) { // if (!session.hasConnection(this) || true) { writeBytes(nodeSession.getToken()); // session.addConnection(this); // } else // writeBytes(new byte[] {}); } } public int getSocketTimeout() { return socketTimeout; } public void setSocketTimeout(int socketTimeout) { this.socketTimeout = socketTimeout; } }