/** * Takes care of properly storing and retrieving file chunks from/to cache. Each chunk's key is * composed of the file path and the chunk's number. The value is a byte array, which is either * chunkSize bytes long or less than that in the case of the last chunk. * * @author Marko Luksa */ class FileChunkMapper { private static final Log log = LogFactory.getLog(FileChunkMapper.class); private final GridFile file; private final Cache<String, byte[]> cache; public FileChunkMapper(GridFile file, Cache<String, byte[]> cache) { this.file = file; this.cache = cache; } public int getChunkSize() { return file.getChunkSize(); } public byte[] fetchChunk(int chunkNumber) { String key = getChunkKey(chunkNumber); byte[] val = cache.get(key); if (log.isTraceEnabled()) log.trace("fetching key=" + key + ": " + (val != null ? val.length + " bytes" : "null")); return val; } public void storeChunk(int chunkNumber, byte[] buffer, int length) { String key = getChunkKey(chunkNumber); byte[] val = trim(buffer, length); cache.put(key, val); if (log.isTraceEnabled()) log.trace("put(): key=" + key + ": " + val.length + " bytes"); } public void removeChunk(int chunkNumber) { cache.remove(getChunkKey(chunkNumber)); } private byte[] trim(byte[] buffer, int length) { byte[] val = new byte[length]; System.arraycopy(buffer, 0, val, 0, length); return val; } private String getChunkKey(int chunkNumber) { return getChunkKey(file.getAbsolutePath(), chunkNumber); } static String getChunkKey(String absoluteFilePath, int chunkNumber) { return absoluteFilePath + ".#" + chunkNumber; } }
@BeforeClass @Parameters(value = {"channel.conf", "use_blocking"}) protected void initializeBase( @Optional("udp.xml") String chconf, @Optional("false") String use_blocking) throws Exception { log = LogFactory.getLog(this.getClass()); Test annotation = this.getClass().getAnnotation(Test.class); // this should never ever happen! if (annotation == null) throw new Exception("Test is not marked with @Test annotation"); StackType type = Util.getIpStackType(); bind_addr = type == StackType.IPv6 ? "::1" : "127.0.0.1"; this.channel_conf = chconf; bind_addr = Util.getProperty(new String[] {Global.BIND_ADDR}, null, "bind_addr", bind_addr); System.setProperty(Global.BIND_ADDR, bind_addr); }
/** * Discovery protocol based on Rackspace Cloud Files storage solution * * @author Gustavo Fernandes */ public class RACKSPACE_PING extends FILE_PING { protected static final Log log = LogFactory.getLog(RACKSPACE_PING.class); private static final String UKService = "https://lon.auth.api.rackspacecloud.com/v1.0"; private static final String USService = "https://auth.api.rackspacecloud.com/v1.0"; protected RackspaceClient rackspaceClient = null; @Property(description = "Rackspace username") protected String username = null; @Property(description = "Rackspace API access key", exposeAsManagedAttribute = false) protected String apiKey = null; @Property(description = "Rackspace region, either UK or US") protected String region = null; @Property(description = "Name of the root container") protected String container = "jgroups"; @Override public void init() throws Exception { if (username == null) { throw new IllegalArgumentException("Rackspace 'username' must not be null"); } if (apiKey == null) { throw new IllegalArgumentException("Rackspace 'apiKey' must not be null"); } if (region == null || (!region.equals("UK") && !region.equals("US"))) { throw new IllegalArgumentException("Invalid 'region', must be UK or US"); } URL authURL = new URL(region.equals("UK") ? UKService : USService); rackspaceClient = new RackspaceClient(authURL, username, apiKey); super.init(); } @Override protected void createRootDir() { rackspaceClient.authenticate(); rackspaceClient.createContainer(container); } @Override protected void readAll(List<Address> members, String clustername, Responses responses) { try { List<String> objects = rackspaceClient.listObjects(container); for (String object : objects) { List<PingData> list = null; byte[] bytes = rackspaceClient.readObject(container, object); if ((list = read(new ByteArrayInputStream(bytes))) == null) { log.warn("failed reading " + object); continue; } for (PingData data : list) { if (members == null || members.contains(data.getAddress())) responses.addResponse(data, data.isCoord()); if (local_addr != null && !local_addr.equals(data.getAddress())) addDiscoveryResponseToCaches( data.getAddress(), data.getLogicalName(), data.getPhysicalAddr()); } } } catch (Exception e) { log.error("Error unmarshalling object", e); } } @Override protected void write(List<PingData> list, String clustername) { try { String filename = clustername + "/" + addressToFilename(local_addr); ByteArrayOutputStream out = new ByteArrayOutputStream(4096); write(list, out); byte[] data = out.toByteArray(); rackspaceClient.createObject(container, filename, data); } catch (Exception e) { log.error("Error marshalling object", e); } } @Override protected void remove(String clustername, Address addr) { String fileName = clustername + "/" + addressToFilename(addr); rackspaceClient.deleteObject(container, fileName); } @Override protected void removeAll(String clustername) { List<String> objects = rackspaceClient.listObjects(container); for (String objName : objects) { rackspaceClient.deleteObject(container, objName); } } /** A thread safe Rackspace ReST client */ static class RackspaceClient { private static final String ACCEPT_HEADER = "Accept"; private static final String AUTH_HEADER = "X-Auth-User"; private static final String AUTH_KEY_HEADER = "X-Auth-Key"; private static final String STORAGE_TOKEN_HEADER = "X-Storage-Token"; private static final String STORAGE_URL_HEADER = "X-Storage-Url"; private static final String CONTENT_LENGTH_HEADER = "Content-Length"; private final URL apiEndpoint; private final String username; private final String apiKey; private volatile Credentials credentials = null; /** * Constructor * * @param apiEndpoint UK or US authentication endpoint * @param username Rackspace username * @param apiKey Rackspace apiKey */ public RackspaceClient(URL apiEndpoint, String username, String apiKey) { this.apiEndpoint = apiEndpoint; this.username = username; this.apiKey = apiKey; } /** Authenticate */ public void authenticate() { HttpURLConnection urlConnection = new ConnBuilder(apiEndpoint) .addHeader(AUTH_HEADER, username) .addHeader(AUTH_KEY_HEADER, apiKey) .getConnection(); Response response = doAuthOperation(urlConnection); if (response.isSuccessCode()) { credentials = new Credentials( response.getHeader(STORAGE_TOKEN_HEADER), response.getHeader(STORAGE_URL_HEADER)); log.trace("Authentication successful"); } else { throw new IllegalStateException( "Error authenticating to the service. Please check your credentials. Code = " + response.code); } } /** * Delete a object (=file) from the storage * * @param containerName Folder name * @param objectName File name */ public void deleteObject(String containerName, String objectName) { HttpURLConnection urlConnection = new ConnBuilder(credentials, containerName, objectName).method("DELETE").getConnection(); Response response = doVoidOperation(urlConnection); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); deleteObject(containerName, objectName); } else { log.error( "Error deleting object " + objectName + " from container " + containerName + ",code = " + response.code); } } } /** * Create a container, which is equivalent to a bucket * * @param containerName Name of the container */ public void createContainer(String containerName) { HttpURLConnection urlConnection = new ConnBuilder(credentials, containerName, null).method("PUT").getConnection(); Response response = doVoidOperation(urlConnection); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); createContainer(containerName); } else { log.error("Error creating container " + containerName + " ,code = " + response.code); } } } /** * Create an object (=file) * * @param containerName Name of the container * @param objectName Name of the file * @param contents Binary content of the file */ public void createObject(String containerName, String objectName, byte[] contents) { HttpURLConnection conn = new ConnBuilder(credentials, containerName, objectName) .method("PUT") .addHeader(CONTENT_LENGTH_HEADER, String.valueOf(contents.length)) .getConnection(); Response response = doSendOperation(conn, contents); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); createObject(containerName, objectName, contents); } else { log.error( "Error creating object " + objectName + " in container " + containerName + ",code = " + response.code); } } } /** * Read the content of a file * * @param containerName Name of the folder * @param objectName name of the file * @return Content of the files */ public byte[] readObject(String containerName, String objectName) { HttpURLConnection urlConnection = new ConnBuilder(credentials, containerName, objectName).getConnection(); Response response = doReadOperation(urlConnection); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); return readObject(containerName, objectName); } else { log.error( "Error reading object " + objectName + " from container " + containerName + ", code = " + response.code); } } return response.payload; } /** * List files in a folder * * @param containerName Folder name * @return List of file names */ public List<String> listObjects(String containerName) { HttpURLConnection urlConnection = new ConnBuilder(credentials, containerName, null).getConnection(); Response response = doReadOperation(urlConnection); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); return listObjects(containerName); } else { log.error("Error listing container " + containerName + ", code = " + response.code); } } return response.payloadAsLines(); } /** * Do a http operation * * @param urlConnection the HttpURLConnection to be used * @param inputData if not null,will be written to the urlconnection. * @param hasOutput if true, read content back from the urlconnection * @return Response */ private Response doOperation( HttpURLConnection urlConnection, byte[] inputData, boolean hasOutput) { Response response = null; InputStream inputStream = null; OutputStream outputStream = null; byte[] payload = null; try { if (inputData != null) { urlConnection.setDoOutput(true); outputStream = urlConnection.getOutputStream(); outputStream.write(inputData); } if (hasOutput) { inputStream = urlConnection.getInputStream(); payload = Util.readFileContents(urlConnection.getInputStream()); } response = new Response(urlConnection.getHeaderFields(), urlConnection.getResponseCode(), payload); } catch (IOException e) { log.error("Error calling service", e); } finally { Util.close(inputStream); Util.close(outputStream); } return response; } /** * Do a http auth operation, will not handle 401 permission denied errors * * @param urlConnection the HttpURLConnection to be used * @return Response Response */ private Response doAuthOperation(HttpURLConnection urlConnection) { return doOperation(urlConnection, null, false); } /** * Do a operation that does not write or read from HttpURLConnection, except for the headers * * @param urlConnection the connection * @return Response */ private Response doVoidOperation(HttpURLConnection urlConnection) { return doOperation(urlConnection, null, false); } /** * Do a operation that writes content to the HttpURLConnection * * @param urlConnection the connection * @param content The content to send * @return Response */ private Response doSendOperation(HttpURLConnection urlConnection, byte[] content) { return doOperation(urlConnection, content, false); } /** * Do a operation that reads from the httpconnection * * @param urlConnection The connections * @return Response */ private Response doReadOperation(HttpURLConnection urlConnection) { return doOperation(urlConnection, null, true); } /** Build HttpURLConnections with adequate headers and method */ private class ConnBuilder { private HttpURLConnection con; public ConnBuilder(URL url) { try { con = (HttpURLConnection) url.openConnection(); } catch (IOException e) { log.error("Error building URL", e); } } public ConnBuilder(Credentials credentials, String container, String object) { try { String url = credentials.storageURL + "/" + container; if (object != null) { url = url + "/" + object; } con = (HttpURLConnection) new URL(url).openConnection(); con.addRequestProperty(STORAGE_TOKEN_HEADER, credentials.authToken); con.addRequestProperty(ACCEPT_HEADER, "*/*"); } catch (IOException e) { log.error("Error creating connection", e); } } public ConnBuilder method(String method) { try { con.setRequestMethod(method); } catch (ProtocolException e) { log.error("Protocol error", e); } return this; } public ConnBuilder addHeader(String key, String value) { con.setRequestProperty(key, value); return this; } public HttpURLConnection getConnection() { return con; } } /** Result of an successfully authenticated session */ private class Credentials { private final String authToken; private final String storageURL; public Credentials(String authToken, String storageURL) { this.authToken = authToken; this.storageURL = storageURL; } } /** Response for a Rackspace API call */ private class Response { private final Map<String, List<String>> headers; private final int code; private final byte[] payload; Response(Map<String, List<String>> headers, int code, byte[] payload) { this.headers = headers; this.code = code; this.payload = payload; } private String getHeader(String name) { return headers.get(name).get(0); } public boolean isSuccessCode() { return code >= 200 && code < 300; } public boolean isAuthDenied() { return code == 401; } public List<String> payloadAsLines() { List<String> lines = new ArrayList<>(); BufferedReader in; try { String line; in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(payload))); while ((line = in.readLine()) != null) { lines.add(line); } in.close(); } catch (IOException e) { log.error("Error reading objects", e); } return lines; } } } }
/** * A Message encapsulates data sent to members of a group. It contains among other things the * address of the sender, the destination address, a payload (byte buffer) and a list of headers. * Headers are added by protocols on the sender side and removed by protocols on the receiver's * side. * * <p>The byte buffer can point to a reference, and we can subset it using index and length. * However, when the message is serialized, we only write the bytes between index and length. * * @since 2.0 * @author Bela Ban */ public class Message implements Streamable { protected Address dest_addr; protected Address src_addr; /** The payload */ protected byte[] buf; /** The index into the payload (usually 0) */ protected int offset; /** The number of bytes in the buffer (usually buf.length is buf not equal to null). */ protected int length; /** All headers are placed here */ protected Headers headers; protected volatile short flags; protected volatile byte transient_flags; // transient_flags is neither marshalled nor copied protected static final Log log = LogFactory.getLog(Message.class); static final byte DEST_SET = 1; static final byte SRC_SET = 1 << 1; static final byte BUF_SET = 1 << 2; // =============================== Flags ==================================== public static enum Flag { OOB((short) 1), // message is out-of-band DONT_BUNDLE((short) (1 << 1)), // don't bundle message at the transport NO_FC((short) (1 << 2)), // bypass flow control SCOPED((short) (1 << 3)), // when a message has a scope NO_RELIABILITY((short) (1 << 4)), // bypass UNICAST(2) and NAKACK NO_TOTAL_ORDER((short) (1 << 5)), // bypass total order (e.g. SEQUENCER) NO_RELAY((short) (1 << 6)), // bypass relaying (RELAY) RSVP((short) (1 << 7)); // ack of a multicast (https://issues.jboss.org/browse/JGRP-1389) final short value; Flag(short value) { this.value = value; } public short value() { return value; } } public static final Flag OOB = Flag.OOB; public static final Flag DONT_BUNDLE = Flag.DONT_BUNDLE; public static final Flag NO_FC = Flag.NO_FC; public static final Flag SCOPED = Flag.SCOPED; public static final Flag NO_RELIABILITY = Flag.NO_RELIABILITY; public static final Flag NO_TOTAL_ORDER = Flag.NO_TOTAL_ORDER; public static final Flag NO_RELAY = Flag.NO_RELAY; public static final Flag RSVP = Flag.RSVP; // =========================== Transient flags ============================== public static enum TransientFlag { OOB_DELIVERED((short) 1); final short value; TransientFlag(short flag) { value = flag; } public short value() { return value; } } public static final TransientFlag OOB_DELIVERED = TransientFlag.OOB_DELIVERED; // OOB which has already been delivered up the stack /** * Constructs a Message given a destination Address * * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member. * <p> */ public Message(Address dest) { setDest(dest); headers = createHeaders(3); } /** * Constructs a Message given a destination Address, a source Address and the payload byte buffer * * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member. * <p> * @param src Address of sender * @param buf Message to be sent. Note that this buffer must not be modified (e.g. buf[0]=0 is not * allowed), since we don't copy the contents on clopy() or clone(). */ public Message(Address dest, Address src, byte[] buf) { this(dest); setSrc(src); setBuffer(buf); } public Message(Address dest, byte[] buf) { this(dest, null, buf); } /** * Constructs a message. The index and length parameters allow to provide a <em>reference</em> to * a byte buffer, rather than a copy, and refer to a subset of the buffer. This is important when * we want to avoid copying. When the message is serialized, only the subset is serialized.<br> * <em> Note that the byte[] buffer passed as argument must not be modified. Reason: if we * retransmit the message, it would still have a ref to the original byte[] buffer passed in as * argument, and so we would retransmit a changed byte[] buffer ! </em> * * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member. * <p> * @param src Address of sender * @param buf A reference to a byte buffer * @param offset The index into the byte buffer * @param length The number of bytes to be used from <tt>buf</tt>. Both index and length are * checked for array index violations and an ArrayIndexOutOfBoundsException will be thrown if * invalid */ public Message(Address dest, Address src, byte[] buf, int offset, int length) { this(dest); setSrc(src); setBuffer(buf, offset, length); } public Message(Address dest, byte[] buf, int offset, int length) { this(dest, null, buf, offset, length); } /** * Constructs a Message given a destination Address, a source Address and the payload Object * * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member. * <p> * @param src Address of sender * @param obj The object will be marshalled into the byte buffer. <em>Obj has to be serializable * (e.g. implementing Serializable, Externalizable or Streamable, or be a basic type (e.g. * Integer, Short etc)).</em> ! The resulting buffer must not be modified (e.g. buf[0]=0 is * not allowed), since we don't copy the contents on clopy() or clone(). * <p> */ public Message(Address dest, Address src, Object obj) { this(dest); setSrc(src); setObject(obj); } public Message(Address dest, Object obj) { this(dest, null, obj); } public Message() { headers = createHeaders(3); } public Message(boolean create_headers) { if (create_headers) headers = createHeaders(3); } public Address getDest() { return dest_addr; } public Address dest() { return dest_addr; } public void setDest(Address new_dest) { dest_addr = new_dest; } public Message dest(Address new_dest) { dest_addr = new_dest; return this; } public Address getSrc() { return src_addr; } public Address src() { return src_addr; } public void setSrc(Address new_src) { src_addr = new_src; } public Message src(Address new_src) { src_addr = new_src; return this; } /** * Returns a <em>reference</em> to the payload (byte buffer). Note that this buffer should not be * modified as we do not copy the buffer on copy() or clone(): the buffer of the copied message is * simply a reference to the old buffer.<br> * Even if offset and length are used: we return the <em>entire</em> buffer, not a subset. */ public byte[] getRawBuffer() { return buf; } /** * Returns a copy of the buffer if offset and length are used, otherwise a reference. * * @return byte array with a copy of the buffer. */ public final byte[] getBuffer() { if (buf == null) return null; if (offset == 0 && length == buf.length) return buf; else { byte[] retval = new byte[length]; System.arraycopy(buf, offset, retval, 0, length); return retval; } } public final Message setBuffer(byte[] b) { buf = b; if (buf != null) { offset = 0; length = buf.length; } else offset = length = 0; return this; } /** * Set the internal buffer to point to a subset of a given buffer * * @param b The reference to a given buffer. If null, we'll reset the buffer to null * @param offset The initial position * @param length The number of bytes */ public final Message setBuffer(byte[] b, int offset, int length) { buf = b; if (buf != null) { if (offset < 0 || offset > buf.length) throw new ArrayIndexOutOfBoundsException(offset); if ((offset + length) > buf.length) throw new ArrayIndexOutOfBoundsException((offset + length)); this.offset = offset; this.length = length; } else this.offset = this.length = 0; return this; } /** * <em> Note that the byte[] buffer passed as argument must not be modified. Reason: if we * retransmit the message, it would still have a ref to the original byte[] buffer passed in as * argument, and so we would retransmit a changed byte[] buffer ! </em> */ public final Message setBuffer(Buffer buf) { if (buf != null) { this.buf = buf.getBuf(); this.offset = buf.getOffset(); this.length = buf.getLength(); } return this; } /** Returns the offset into the buffer at which the data starts */ public int getOffset() { return offset; } /** Returns the number of bytes in the buffer */ public int getLength() { return length; } /** * Returns a reference to the headers hashmap, which is <em>immutable</em>. Any attempt to modify * the returned map will cause a runtime exception */ public Map<Short, Header> getHeaders() { return headers.getHeaders(); } public String printHeaders() { return headers.printHeaders(); } public int getNumHeaders() { return headers != null ? headers.size() : 0; } /** * Takes an object and uses Java serialization to generate the byte[] buffer which is set in the * message. Parameter 'obj' has to be serializable (e.g. implementing Serializable, Externalizable * or Streamable, or be a basic type (e.g. Integer, Short etc)). */ public final Message setObject(Object obj) { if (obj == null) return this; if (obj instanceof byte[]) return setBuffer((byte[]) obj); if (obj instanceof Buffer) return setBuffer((Buffer) obj); try { return setBuffer(Util.objectToByteBuffer(obj)); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } /** * Uses custom serialization to create an object from the buffer of the message. Note that this is * dangerous when using your own classloader, e.g. inside of an application server ! Most likely, * JGroups will use the system classloader to deserialize the buffer into an object, whereas (for * example) a web application will want to use the webapp's classloader, resulting in a * ClassCastException. The recommended way is for the application to use their own serialization * and only pass byte[] buffer to JGroups. * * @return */ public final Object getObject() { try { return Util.objectFromByteBuffer(buf, offset, length); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } /** * Sets a number of flags in a message * * @param flags The flag or flags * @return A reference to the message */ public Message setFlag(Flag... flags) { if (flags != null) for (Flag flag : flags) if (flag != null) this.flags |= flag.value(); return this; } /** * Sets the flags from a short. <em>Not recommended</em> (use {@link * #setFlag(org.jgroups.Message.Flag...)} instead), as the internal representation of flags might * change anytime. * * @param flag * @return */ public Message setFlag(short flag) { flags |= flag; return this; } /** * Returns the internal representation of flags. Don't use this, as the internal format might * change at any time ! This is only used by unit test code * * @return */ public short getFlags() { return flags; } /** * Clears a number of flags in a message * * @param flags The flags * @return A reference to the message */ public Message clearFlag(Flag... flags) { if (flags != null) for (Flag flag : flags) if (flag != null) this.flags &= ~flag.value(); return this; } public static boolean isFlagSet(short flags, Flag flag) { return flag != null && ((flags & flag.value()) == flag.value()); } /** * Checks if a given flag is set * * @param flag The flag * @return Whether of not the flag is curently set */ public boolean isFlagSet(Flag flag) { return isFlagSet(flags, flag); } /** * Same as {@link #setFlag(Flag...)} except that transient flags are not marshalled * * @param flag The flag */ public Message setTransientFlag(TransientFlag... flags) { if (flags != null) for (TransientFlag flag : flags) if (flag != null) transient_flags |= flag.value(); return this; } /** * Atomically checks if a given flag is set and - if not - sets it. When multiple threads * concurrently call this method with the same flag, only one of them will be able to set the flag * * @param flag * @return True if the flag could be set, false if not (was already set) */ public synchronized boolean setTransientFlagIfAbsent(TransientFlag flag) { if (isTransientFlagSet(flag)) return false; setTransientFlag(flag); return true; } public Message clearTransientFlag(TransientFlag... flags) { if (flags != null) for (TransientFlag flag : flags) if (flag != null) transient_flags &= ~flag.value(); return this; } public boolean isTransientFlagSet(TransientFlag flag) { return flag != null && (transient_flags & flag.value()) == flag.value(); } public short getTransientFlags() { return transient_flags; } public Message setScope(short scope) { Util.setScope(this, scope); return this; } public short getScope() { return Util.getScope(this); } /*---------------------- Used by protocol layers ----------------------*/ /** Puts a header given an ID into the hashmap. Overwrites potential existing entry. */ public Message putHeader(short id, Header hdr) { if (id < 0) throw new IllegalArgumentException("An ID of " + id + " is invalid"); headers.putHeader(id, hdr); return this; } /** * Puts a header given a key into the map, only if the key doesn't exist yet * * @param id * @param hdr * @return the previous value associated with the specified key, or <tt>null</tt> if there was no * mapping for the key. (A <tt>null</tt> return can also indicate that the map previously * associated <tt>null</tt> with the key, if the implementation supports null values.) */ public Header putHeaderIfAbsent(short id, Header hdr) { if (id <= 0) throw new IllegalArgumentException("An ID of " + id + " is invalid"); return headers.putHeaderIfAbsent(id, hdr); } public Header getHeader(short id) { if (id <= 0) throw new IllegalArgumentException( "An ID of " + id + " is invalid. Add the protocol which calls " + "getHeader() to jg-protocol-ids.xml"); return headers.getHeader(id); } /*---------------------------------------------------------------------*/ public Message copy() { return copy(true); } /** * Create a copy of the message. If offset and length are used (to refer to another buffer), the * copy will contain only the subset offset and length point to, copying the subset into the new * copy. * * @param copy_buffer * @return Message with specified data */ public Message copy(boolean copy_buffer) { return copy(copy_buffer, true); } /** * Create a copy of the message. If offset and length are used (to refer to another buffer), the * copy will contain only the subset offset and length point to, copying the subset into the new * copy. * * <p>Note that for headers, only the arrays holding references to the headers are copied, not the * headers themselves ! The consequence is that the headers array of the copy hold the *same* * references as the original, so do *not* modify the headers ! If you want to change a header, * copy it and call {@link Message#putHeader(short,Header)} again. * * @param copy_buffer * @param copy_headers Copy the headers * @return Message with specified data */ public Message copy(boolean copy_buffer, boolean copy_headers) { Message retval = new Message(false); retval.dest_addr = dest_addr; retval.src_addr = src_addr; retval.flags = flags; retval.transient_flags = transient_flags; if (copy_buffer && buf != null) { // change bela Feb 26 2004: we don't resolve the reference retval.setBuffer(buf, offset, length); } retval.headers = copy_headers ? createHeaders(headers) : createHeaders(3); return retval; } /** * Doesn't copy any headers except for those with ID >= copy_headers_above * * @param copy_buffer * @param starting_id * @return A message with headers whose ID are >= starting_id */ public Message copy(boolean copy_buffer, short starting_id) { return copy(copy_buffer, starting_id, (short[]) null); } /** * Copies a message. Copies only headers with IDs >= starting_id or IDs which are in the * copy_only_ids list * * @param copy_buffer * @param starting_id * @param copy_only_ids * @return */ public Message copy(boolean copy_buffer, short starting_id, short... copy_only_ids) { Message retval = copy(copy_buffer, false); for (Map.Entry<Short, Header> entry : getHeaders().entrySet()) { short id = entry.getKey(); if (id >= starting_id || containsId(id, copy_only_ids)) retval.putHeader(id, entry.getValue()); } return retval; } public Message makeReply() { Message retval = new Message(src_addr); if (dest_addr != null) retval.setSrc(dest_addr); return retval; } public String toString() { StringBuilder ret = new StringBuilder(64); ret.append("[dst: "); if (dest_addr == null) ret.append("<null>"); else ret.append(dest_addr); ret.append(", src: "); if (src_addr == null) ret.append("<null>"); else ret.append(src_addr); int size; if ((size = getNumHeaders()) > 0) ret.append(" (").append(size).append(" headers)"); ret.append(", size="); if (buf != null && length > 0) ret.append(length); else ret.append('0'); ret.append(" bytes"); if (flags > 0) ret.append(", flags=").append(flagsToString(flags)); if (transient_flags > 0) ret.append(", transient_flags=" + transientFlagsToString()); ret.append(']'); return ret.toString(); } /** Tries to read an object from the message's buffer and prints it */ public String toStringAsObject() { if (buf == null) return null; try { Object obj = getObject(); return obj != null ? obj.toString() : ""; } catch (Exception e) { // it is not an object return ""; } } public String printObjectHeaders() { return headers.printObjectHeaders(); } /* ----------------------------------- Interface Streamable ------------------------------- */ /** * Streams all members (dest and src addresses, buffer and headers) to the output stream. * * @param out * @throws Exception */ public void writeTo(DataOutput out) throws Exception { byte leading = 0; if (dest_addr != null) leading = Util.setFlag(leading, DEST_SET); if (src_addr != null) leading = Util.setFlag(leading, SRC_SET); if (buf != null) leading = Util.setFlag(leading, BUF_SET); // 1. write the leading byte first out.write(leading); // 2. the flags (e.g. OOB, LOW_PRIO), skip the transient flags out.writeShort(flags); // 3. dest_addr if (dest_addr != null) Util.writeAddress(dest_addr, out); // 4. src_addr if (src_addr != null) Util.writeAddress(src_addr, out); // 5. buf if (buf != null) { out.writeInt(length); out.write(buf, offset, length); } // 6. headers int size = headers.size(); out.writeShort(size); final short[] ids = headers.getRawIDs(); final Header[] hdrs = headers.getRawHeaders(); for (int i = 0; i < ids.length; i++) { if (ids[i] > 0) { out.writeShort(ids[i]); writeHeader(hdrs[i], out); } } } /** * Writes the message to the output stream, but excludes the dest and src addresses unless the src * address given as argument is different from the message's src address * * @param src * @param out * @throws Exception */ public void writeToNoAddrs(Address src, DataOutputStream out) throws Exception { byte leading = 0; boolean write_src_addr = src == null || src_addr != null && !src_addr.equals(src); if (write_src_addr) leading = Util.setFlag(leading, SRC_SET); if (buf != null) leading = Util.setFlag(leading, BUF_SET); // 1. write the leading byte first out.write(leading); // 2. the flags (e.g. OOB, LOW_PRIO) out.writeShort(flags); // 4. src_addr if (write_src_addr) Util.writeAddress(src_addr, out); // 5. buf if (buf != null) { out.writeInt(length); out.write(buf, offset, length); } // 6. headers int size = headers.size(); out.writeShort(size); final short[] ids = headers.getRawIDs(); final Header[] hdrs = headers.getRawHeaders(); for (int i = 0; i < ids.length; i++) { if (ids[i] > 0) { out.writeShort(ids[i]); writeHeader(hdrs[i], out); } } } public void readFrom(DataInput in) throws Exception { // 1. read the leading byte first byte leading = in.readByte(); // 2. the flags flags = in.readShort(); // 3. dest_addr if (Util.isFlagSet(leading, DEST_SET)) dest_addr = Util.readAddress(in); // 4. src_addr if (Util.isFlagSet(leading, SRC_SET)) src_addr = Util.readAddress(in); // 5. buf if (Util.isFlagSet(leading, BUF_SET)) { int len = in.readInt(); buf = new byte[len]; in.readFully(buf, 0, len); length = len; } // 6. headers int len = in.readShort(); headers = createHeaders(len); short[] ids = headers.getRawIDs(); Header[] hdrs = headers.getRawHeaders(); for (int i = 0; i < len; i++) { short id = in.readShort(); Header hdr = readHeader(in); ids[i] = id; hdrs[i] = hdr; } } /* --------------------------------- End of Interface Streamable ----------------------------- */ /** * Returns the exact size of the marshalled message. Uses method size() of each header to compute * the size, so if a Header subclass doesn't implement size() we will use an approximation. * However, most relevant header subclasses have size() implemented correctly. (See * org.jgroups.tests.SizeTest). * * @return The number of bytes for the marshalled message */ public long size() { long retval = Global.BYTE_SIZE // leading byte + Global.SHORT_SIZE; // flags if (dest_addr != null) retval += Util.size(dest_addr); if (src_addr != null) retval += Util.size(src_addr); if (buf != null) retval += Global.INT_SIZE // length (integer) + length; // number of bytes in the buffer retval += Global.SHORT_SIZE; // number of headers retval += headers.marshalledSize(); return retval; } /* ----------------------------------- Private methods ------------------------------- */ public static String flagsToString(short flags) { StringBuilder sb = new StringBuilder(); boolean first = true; if (isFlagSet(flags, Flag.OOB)) { first = false; sb.append("OOB"); } if (isFlagSet(flags, Flag.DONT_BUNDLE)) { if (!first) sb.append("|"); else first = false; sb.append("DONT_BUNDLE"); } if (isFlagSet(flags, Flag.NO_FC)) { if (!first) sb.append("|"); else first = false; sb.append("NO_FC"); } if (isFlagSet(flags, Flag.SCOPED)) { if (!first) sb.append("|"); else first = false; sb.append("SCOPED"); } if (isFlagSet(flags, Flag.NO_RELIABILITY)) { if (!first) sb.append("|"); else first = false; sb.append("NO_RELIABILITY"); } if (isFlagSet(flags, Flag.NO_TOTAL_ORDER)) { if (!first) sb.append("|"); else first = false; sb.append("NO_TOTAL_ORDER"); } if (isFlagSet(flags, Flag.NO_RELAY)) { if (!first) sb.append("|"); else first = false; sb.append("NO_RELAY"); } if (isFlagSet(flags, Flag.RSVP)) { if (!first) sb.append("|"); else first = false; sb.append("RSVP"); } return sb.toString(); } public String transientFlagsToString() { StringBuilder sb = new StringBuilder(); if (isTransientFlagSet(TransientFlag.OOB_DELIVERED)) sb.append("OOB_DELIVERED"); return sb.toString(); } protected static void writeHeader(Header hdr, DataOutput out) throws Exception { short magic_number = ClassConfigurator.getMagicNumber(hdr.getClass()); out.writeShort(magic_number); hdr.writeTo(out); } protected static boolean containsId(short id, short[] ids) { if (ids == null) return false; for (short tmp : ids) if (tmp == id) return true; return false; } protected static Header readHeader(DataInput in) throws Exception { short magic_number = in.readShort(); Class clazz = ClassConfigurator.get(magic_number); if (clazz == null) throw new IllegalArgumentException( "magic number " + magic_number + " is not available in magic map"); Header hdr = (Header) clazz.newInstance(); hdr.readFrom(in); return hdr; } protected static Headers createHeaders(int size) { return size > 0 ? new Headers(size) : new Headers(3); } protected static Headers createHeaders(Headers m) { return new Headers(m); } /* ------------------------------- End of Private methods ---------------------------- */ }
/** * Implementation of {@link TimeScheduler}. Uses a hashed timing wheel [1]. * * <p>[1] http://www.cse.wustl.edu/~cdgill/courses/cs6874/TimingWheels.ppt * * @author Bela Ban */ @Experimental @Unsupported public class HashedTimingWheel implements TimeScheduler, Runnable { private final ThreadPoolExecutor pool; private Thread runner = null; private final Lock lock = new ReentrantLock(); protected volatile boolean running; protected static final Log log = LogFactory.getLog(HashedTimingWheel.class); protected ThreadFactory timer_thread_factory = null; protected int wheel_size = 200; // number of ticks on the timing wheel protected long tick_time = 50L; // number of milliseconds a tick has protected final long ROTATION_TIME; // time for 1 lap protected final List<MyTask>[] wheel; protected int wheel_position = 0; // current position of the wheel, run() advances it by one (every TICK_TIME ms) /** Create a scheduler that executes tasks in dynamically adjustable intervals */ @SuppressWarnings("unchecked") public HashedTimingWheel() { ROTATION_TIME = wheel_size * tick_time; wheel = new List[wheel_size]; pool = new ThreadPoolExecutor( 4, 10, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(5000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); init(); } @SuppressWarnings("unchecked") public HashedTimingWheel( ThreadFactory factory, int min_threads, int max_threads, long keep_alive_time, int max_queue_size, int wheel_size, long tick_time) { this.wheel_size = wheel_size; this.tick_time = tick_time; ROTATION_TIME = wheel_size * tick_time; wheel = new List[this.wheel_size]; timer_thread_factory = factory; pool = new ThreadPoolExecutor( min_threads, max_threads, keep_alive_time, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(max_queue_size), factory, new ThreadPoolExecutor.CallerRunsPolicy()); init(); } @SuppressWarnings("unchecked") public HashedTimingWheel(int corePoolSize) { ROTATION_TIME = wheel_size * tick_time; wheel = (List<MyTask>[]) new List[wheel_size]; pool = new ThreadPoolExecutor( corePoolSize, corePoolSize * 2, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(5000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); init(); } public void setThreadFactory(ThreadFactory factory) { pool.setThreadFactory(factory); } public int getMinThreads() { return pool.getCorePoolSize(); } public void setMinThreads(int size) { pool.setCorePoolSize(size); } public int getMaxThreads() { return pool.getMaximumPoolSize(); } public void setMaxThreads(int size) { pool.setMaximumPoolSize(size); } public long getKeepAliveTime() { return pool.getKeepAliveTime(TimeUnit.MILLISECONDS); } public void setKeepAliveTime(long time) { pool.setKeepAliveTime(time, TimeUnit.MILLISECONDS); } public int getCurrentThreads() { return pool.getPoolSize(); } public int getQueueSize() { return pool.getQueue().size(); } public String dumpTimerTasks() { StringBuilder sb = new StringBuilder(); lock.lock(); try { for (List<MyTask> list : wheel) { if (!list.isEmpty()) { sb.append(list).append("\n"); } } } finally { lock.unlock(); } return sb.toString(); } public void execute(Runnable task) { schedule(task, 0, TimeUnit.MILLISECONDS); } public Future<?> schedule(Runnable work, long delay, TimeUnit unit) { if (work == null) throw new NullPointerException(); if (isShutdown() || !running) return null; MyTask retval = null; long time = TimeUnit.MILLISECONDS.convert(delay, unit); // execution time lock.lock(); try { int num_ticks = (int) Math.max(1, ((time % ROTATION_TIME) / tick_time)); int position = (wheel_position + num_ticks) % wheel_size; int rounds = (int) (time / ROTATION_TIME); List<MyTask> list = wheel[position]; retval = new MyTask(work, rounds); list.add(retval); } finally { lock.unlock(); } return retval; } public Future<?> scheduleWithFixedDelay( Runnable task, long initial_delay, long delay, TimeUnit unit) { if (task == null) throw new NullPointerException(); if (isShutdown() || !running) return null; RecurringTask wrapper = new FixedIntervalTask(task, delay); wrapper.doSchedule(initial_delay); return wrapper; } public Future<?> scheduleAtFixedRate( Runnable task, long initial_delay, long delay, TimeUnit unit) { if (task == null) throw new NullPointerException(); if (isShutdown() || !running) return null; RecurringTask wrapper = new FixedRateTask(task, delay); wrapper.doSchedule(initial_delay); return wrapper; } /** * Schedule a task for execution at varying intervals. After execution, the task will get * rescheduled after {@link org.jgroups.util.HashedTimingWheel.Task#nextInterval()} milliseconds. * The task is neve done until nextInterval() return a value <= 0 or the task is cancelled. * * @param task the task to execute Task is rescheduled relative to the last time it * <i>actually</i> started execution * <p><tt>false</tt>:<br> * Task is scheduled relative to its <i>last</i> execution schedule. This has the effect that * the time between two consecutive executions of the task remains the same. * <p>Note that relative is always true; we always schedule the next execution relative to the * last *actual* */ public Future<?> scheduleWithDynamicInterval(Task task) { if (task == null) throw new NullPointerException(); if (isShutdown() || !running) return null; RecurringTask task_wrapper = new DynamicIntervalTask(task); task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor return task_wrapper; } /** * Returns the number of tasks currently in the timer * * @return The number of tasks currently in the timer */ public int size() { int retval = 0; lock.lock(); try { for (List<MyTask> list : wheel) retval += list.size(); return retval; } finally { lock.unlock(); } } public String toString() { return getClass().getSimpleName(); } /** * Stops the timer, cancelling all tasks * * @throws InterruptedException if interrupted while waiting for thread to return */ public void stop() { stopRunner(); List<Runnable> remaining_tasks = pool.shutdownNow(); for (Runnable task : remaining_tasks) { if (task instanceof Future) { Future future = (Future) task; future.cancel(true); } } pool.getQueue().clear(); try { pool.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { } } public boolean isShutdown() { return pool.isShutdown(); } public void run() { final long base_time = System.currentTimeMillis(); long next_time, sleep_time; long cnt = 0; while (running) { try { _run(); next_time = base_time + (++cnt * tick_time); sleep_time = Math.max(0, next_time - System.currentTimeMillis()); Util.sleep(sleep_time); } catch (Throwable t) { log.error(Util.getMessage("FailedExecutingTasksS"), t); } } } protected void _run() { lock.lock(); try { wheel_position = (wheel_position + 1) % wheel_size; List<MyTask> list = wheel[wheel_position]; if (list.isEmpty()) return; for (Iterator<MyTask> it = list.iterator(); it.hasNext(); ) { MyTask tmp = it.next(); if (tmp.getAndDecrementRound() <= 0) { try { pool.execute(tmp); } catch (Throwable t) { log.error(Util.getMessage("FailureSubmittingTaskToThreadPool"), t); } it.remove(); } } } finally { lock.unlock(); } } protected void init() { for (int i = 0; i < wheel.length; i++) wheel[i] = new LinkedList<>(); startRunner(); } protected void startRunner() { running = true; runner = timer_thread_factory != null ? timer_thread_factory.newThread(this, "Timer runner") : new Thread(this, "Timer runner"); runner.start(); } protected void stopRunner() { lock.lock(); try { running = false; for (List<MyTask> list : wheel) { if (!list.isEmpty()) { for (MyTask task : list) task.cancel(true); list.clear(); } } } finally { lock.unlock(); } } /** Simple task wrapper, always executed by at most 1 thread. */ protected static class MyTask implements Future, Runnable { protected final Runnable task; protected volatile boolean cancelled = false; protected volatile boolean done = false; protected MyTask next; protected int round; public MyTask(Runnable task, int round) { this.task = task; this.round = round; } public int getRound() { return round; } public int getAndDecrementRound() { return round--; } public void setRound(int round) { this.round = round; } public boolean cancel(boolean mayInterruptIfRunning) { boolean retval = !isDone(); cancelled = true; return retval; } public boolean isCancelled() { return cancelled; } public boolean isDone() { return done || cancelled; } public Object get() throws InterruptedException, ExecutionException { return null; } public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } public void run() { if (isDone()) return; try { task.run(); } catch (Throwable t) { log.error(Util.getMessage("FailedExecutingTask") + task, t); } finally { done = true; } } public String toString() { return task.toString(); } } /** * Task which executes multiple times. An instance of this class wraps the real task and * intercepts run(): when called, it forwards the call to task.run() and then schedules another * execution (until cancelled). The {@link #nextInterval()} method determines the time to wait * until the next execution. * * @param <V> */ private abstract class RecurringTask<V> implements Runnable, Future<V> { protected final Runnable task; protected volatile Future<?> future; // cannot be null ! protected volatile boolean cancelled = false; public RecurringTask(Runnable task) { this.task = task; } /** * The time to wait until the next execution * * @return Number of milliseconds to wait until the next execution is scheduled */ protected abstract long nextInterval(); protected boolean rescheduleOnZeroDelay() { return false; } public void doSchedule() { long next_interval = nextInterval(); if (next_interval <= 0 && !rescheduleOnZeroDelay()) { if (log.isTraceEnabled()) log.trace("task will not get rescheduled as interval is " + next_interval); return; } future = schedule(this, next_interval, TimeUnit.MILLISECONDS); if (cancelled) future.cancel(true); } public void doSchedule(long next_interval) { future = schedule(this, next_interval, TimeUnit.MILLISECONDS); if (cancelled) future.cancel(true); } public void run() { if (cancelled) { if (future != null) future.cancel(true); return; } try { task.run(); } catch (Throwable t) { log.error(Util.getMessage("FailedRunningTask") + task, t); } if (!cancelled) doSchedule(); } public boolean cancel(boolean mayInterruptIfRunning) { boolean retval = !isDone(); cancelled = true; if (future != null) future.cancel(mayInterruptIfRunning); return retval; } public boolean isCancelled() { return cancelled; } public boolean isDone() { return cancelled || (future == null || future.isDone()); } public V get() throws InterruptedException, ExecutionException { return null; } public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName() + ": task=" + task + ", cancelled=" + isCancelled()); return sb.toString(); } } private class FixedIntervalTask<V> extends RecurringTask<V> { final long interval; public FixedIntervalTask(Runnable task, long interval) { super(task); this.interval = interval; } protected long nextInterval() { return interval; } } private class FixedRateTask<V> extends RecurringTask<V> { final long interval; final long first_execution; int num_executions = 0; public FixedRateTask(Runnable task, long interval) { super(task); this.interval = interval; this.first_execution = System.currentTimeMillis(); } protected long nextInterval() { long target_time = first_execution + (interval * ++num_executions); return target_time - System.currentTimeMillis(); } protected boolean rescheduleOnZeroDelay() { return true; } } private class DynamicIntervalTask<V> extends RecurringTask<V> { public DynamicIntervalTask(Task task) { super(task); } protected long nextInterval() { if (task instanceof Task) return ((Task) task).nextInterval(); return 0; } } }
/** * Discovery protocol based on Openstack Swift (object storage). * * <p>This implementation is derived from Gustavo Fernandes work on RACKSPACE_PING * * @author tsegismont * @since 3.1 */ @Experimental public class SWIFT_PING extends FILE_PING { private static final Log log = LogFactory.getLog(SWIFT_PING.class); protected SwiftClient swiftClient = null; @Property(description = "Authentication url") protected String auth_url = null; @Property(description = "Authentication type") protected String auth_type = "keystone_v_2_0"; @Property(description = "Openstack Keystone tenant name") protected String tenant = null; @Property(description = "Username") protected String username = null; @Property(description = "Password", exposeAsManagedAttribute = false) protected String password = null; @Property(description = "Name of the root container") protected String container = "jgroups"; @Override public void init() throws Exception { Utils.validateNotEmpty(auth_url, "auth_url"); Utils.validateNotEmpty(auth_type, "auth_type"); Utils.validateNotEmpty(username, "username"); Utils.validateNotEmpty(password, "password"); Utils.validateNotEmpty(container, "container"); Authenticator authenticator = createAuthenticator(); authenticator.validateParams(); swiftClient = new SwiftClient(authenticator); // Authenticate now to record credential swiftClient.authenticate(); super.init(); } private Authenticator createAuthenticator() throws Exception { AUTH_TYPE authType = AUTH_TYPE.getByConfigName(auth_type); if (authType == null) { throw new IllegalArgumentException("Invalid 'auth_type' : " + auth_type); } URL authUrl = new URL(auth_url); Authenticator authenticator = null; switch (authType) { case KEYSTONE_V_2_0: authenticator = new Keystone_V_2_0_Auth(tenant, authUrl, username, password); break; default: // We shouldn't come here since we checked auth_type throw new IllegalStateException("Could not select authenticator"); } return authenticator; } @Override protected void createRootDir() { try { swiftClient.createContainer(container); } catch (Exception e) { log.error("failure creating container", e); } } @Override protected void readAll(List<Address> members, String clustername, Responses responses) { try { List<String> objects = swiftClient.listObjects(container); for (String object : objects) { List<PingData> list = null; byte[] bytes = swiftClient.readObject(container, object); if ((list = read(new ByteArrayInputStream(bytes))) == null) { log.warn("failed reading " + object); continue; } for (PingData data : list) { if (members == null || members.contains(data.getAddress())) responses.addResponse(data, data.isCoord()); if (local_addr != null && !local_addr.equals(data.getAddress())) addDiscoveryResponseToCaches( data.getAddress(), data.getLogicalName(), data.getPhysicalAddr()); } } } catch (Exception e) { log.error("Error unmarshalling object", e); } } @Override protected void write(List<PingData> list, String clustername) { try { String filename = clustername + "/" + addressToFilename(local_addr); ByteArrayOutputStream out = new ByteArrayOutputStream(4096); write(list, out); byte[] data = out.toByteArray(); swiftClient.createObject(container, filename, data); } catch (Exception e) { log.error("Error marshalling object", e); } } @Override protected void remove(String clustername, Address addr) { String fileName = clustername + "/" + addressToFilename(addr); try { swiftClient.deleteObject(container, fileName); } catch (Exception e) { log.error("failure removing data", e); } } private static class HttpHeaders { private static final String CONTENT_TYPE_HEADER = "Content-type"; private static final String ACCEPT_HEADER = "Accept"; // // private static final String AUTH_HEADER = "X-Auth-User"; // // private static final String AUTH_KEY_HEADER = "X-Auth-Key"; // private static final String STORAGE_TOKEN_HEADER = "X-Storage-Token"; // // private static final String STORAGE_URL_HEADER = "X-Storage-Url"; private static final String CONTENT_LENGTH_HEADER = "Content-Length"; } /** Supported Swift authentication providers */ private static enum AUTH_TYPE { KEYSTONE_V_2_0("keystone_v_2_0"); private static final Map<String, AUTH_TYPE> LOOKUP = new HashMap<String, AUTH_TYPE>(); static { for (AUTH_TYPE type : EnumSet.allOf(AUTH_TYPE.class)) LOOKUP.put(type.configName, type); } private String configName; private AUTH_TYPE(String externalName) { this.configName = externalName; } public static AUTH_TYPE getByConfigName(String configName) { return LOOKUP.get(configName); } } /** Result of a successfully authenticated session */ private static class Credentials { private final String authToken; private final String storageUrl; public Credentials(String authToken, String storageUrl) { this.authToken = authToken; this.storageUrl = storageUrl; } } /** Contract for Swift authentication providers */ private static interface Authenticator { /** Validate SWIFT_PING config parameters */ void validateParams(); Credentials authenticate() throws Exception; } /** Openstack Keytsone v2.0 authentication provider. Thread safe implementation */ private static class Keystone_V_2_0_Auth implements Authenticator { private static XPathExpression tokenIdExpression; private static XPathExpression publicUrlExpression; static { XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpath = xPathFactory.newXPath(); try { tokenIdExpression = xpath.compile("/access/token/@id"); publicUrlExpression = xpath.compile( "/access/serviceCatalog/service[@type='object-store']/endpoint/@publicURL"); } catch (XPathExpressionException e) { // Do nothing } } private String tenant; private URL authUrl; private String username; private String password; public Keystone_V_2_0_Auth(String tenant, URL authUrl, String username, String password) { this.tenant = tenant; this.authUrl = authUrl; this.username = username; this.password = password; } public void validateParams() { // All others params already validated Utils.validateNotEmpty(tenant, "tenant"); } public Credentials authenticate() throws Exception { HttpURLConnection urlConnection = new ConnBuilder(authUrl) .addHeader(HttpHeaders.CONTENT_TYPE_HEADER, "application/json") .addHeader(HttpHeaders.ACCEPT_HEADER, "application/xml") .getConnection(); StringBuilder jsonBuilder = new StringBuilder(); jsonBuilder .append("{\"auth\": {\"tenantName\": \"") .append(tenant) .append("\", \"passwordCredentials\": {\"username\": \"") .append(username) .append("\", \"password\": \"") .append(password) .append("\"}}}"); HttpResponse response = Utils.doOperation(urlConnection, jsonBuilder.toString().getBytes(), true); if (response.isSuccessCode()) { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); Document doc = builder.parse(new ByteArrayInputStream(response.payload)); String authToken = (String) tokenIdExpression.evaluate(doc, XPathConstants.STRING); String storageUrl = (String) publicUrlExpression.evaluate(doc, XPathConstants.STRING); log.trace("Authentication successful"); return new Credentials(authToken, storageUrl); } else { throw new IllegalStateException( "Error authenticating to the service. Please check your credentials. Code = " + response.code); } } } /** Build HttpURLConnections with adequate headers and method */ private static class ConnBuilder { private HttpURLConnection con; public ConnBuilder(URL url) { try { con = (HttpURLConnection) url.openConnection(); } catch (IOException e) { log.error("Error building URL", e); } } public ConnBuilder(Credentials credentials, String container, String object) { try { String url = credentials.storageUrl + "/" + container; if (object != null) { url = url + "/" + object; } con = (HttpURLConnection) new URL(url).openConnection(); } catch (IOException e) { log.error("Error creating connection", e); } } public ConnBuilder method(String method) { try { con.setRequestMethod(method); } catch (ProtocolException e) { log.error("Protocol error", e); } return this; } public ConnBuilder addHeader(String key, String value) { con.setRequestProperty(key, value); return this; } public HttpURLConnection getConnection() { return con; } } /** Response for a Swift API call */ private static class HttpResponse { // For later use private final Map<String, List<String>> headers; private final int code; private final byte[] payload; HttpResponse(Map<String, List<String>> headers, int code, byte[] payload) { this.headers = headers; this.code = code; this.payload = payload; } public List<String> payloadAsLines() { List<String> lines = new ArrayList<String>(); BufferedReader in; try { String line; in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(payload))); while ((line = in.readLine()) != null) { lines.add(line); } in.close(); } catch (IOException e) { log.error("Error reading objects", e); } return lines; } public boolean isSuccessCode() { return Utils.isSuccessCode(code); } public boolean isAuthDenied() { return Utils.isAuthDenied(code); } } /** A thread safe Swift client */ protected static class SwiftClient { private Authenticator authenticator; private volatile Credentials credentials = null; /** * Constructor * * @param authenticator Swift auth provider */ public SwiftClient(Authenticator authenticator) { this.authenticator = authenticator; } /** * Authenticate * * @throws Exception */ public void authenticate() throws Exception { credentials = authenticator.authenticate(); } /** * Delete a object (=file) from the storage * * @param containerName Folder name * @param objectName File name * @throws IOException */ public void deleteObject(String containerName, String objectName) throws Exception { HttpURLConnection urlConnection = getConnBuilder(containerName, objectName).method("DELETE").getConnection(); HttpResponse response = Utils.doVoidOperation(urlConnection); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); deleteObject(containerName, objectName); } else { log.error( "Error deleting object " + objectName + " from container " + containerName + ",code = " + response.code); } } } /** * Create a container, which is equivalent to a bucket * * @param containerName Name of the container * @throws IOException */ public void createContainer(String containerName) throws Exception { HttpURLConnection urlConnection = getConnBuilder(containerName, null).method("PUT").getConnection(); HttpResponse response = Utils.doVoidOperation(urlConnection); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); createContainer(containerName); } else { log.error("Error creating container " + containerName + " ,code = " + response.code); } } } /** * Create an object (=file) * * @param containerName Name of the container * @param objectName Name of the file * @param contents Binary content of the file * @throws IOException */ public void createObject(String containerName, String objectName, byte[] contents) throws Exception { HttpURLConnection conn = getConnBuilder(containerName, objectName) .method("PUT") .addHeader(HttpHeaders.CONTENT_LENGTH_HEADER, String.valueOf(contents.length)) .getConnection(); HttpResponse response = Utils.doSendOperation(conn, contents); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); createObject(containerName, objectName, contents); } else { log.error( "Error creating object " + objectName + " in container " + containerName + ",code = " + response.code); } } } /** * Read the content of a file * * @param containerName Name of the folder * @param objectName name of the file * @return Content of the files * @throws IOException */ public byte[] readObject(String containerName, String objectName) throws Exception { HttpURLConnection urlConnection = getConnBuilder(containerName, objectName).getConnection(); HttpResponse response = Utils.doReadOperation(urlConnection); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); return readObject(containerName, objectName); } else { log.error( "Error reading object " + objectName + " from container " + containerName + ", code = " + response.code); } } return response.payload; } /** * List files in a folder * * @param containerName Folder name * @return List of file names * @throws IOException */ public List<String> listObjects(String containerName) throws Exception { HttpURLConnection urlConnection = getConnBuilder(containerName, null).getConnection(); HttpResponse response = Utils.doReadOperation(urlConnection); if (!response.isSuccessCode()) { if (response.isAuthDenied()) { log.warn("Refreshing credentials and retrying"); authenticate(); return listObjects(containerName); } else { log.error("Error listing container " + containerName + ", code = " + response.code); } } return response.payloadAsLines(); } private ConnBuilder getConnBuilder(String container, String object) { ConnBuilder connBuilder = new ConnBuilder(credentials, container, object); connBuilder.addHeader(HttpHeaders.STORAGE_TOKEN_HEADER, credentials.authToken); connBuilder.addHeader(HttpHeaders.ACCEPT_HEADER, "*/*"); return connBuilder; } } private static class Utils { public static void validateNotEmpty(String arg, String argname) { if (arg == null || arg.trim().length() == 0) { throw new IllegalArgumentException("'" + argname + "' cannot be empty"); } } /** * Is http response code in success range ? * * @param code * @return */ public static boolean isSuccessCode(int code) { return code >= 200 && code < 300; } /** * Is http Unauthorized response code ? * * @param code * @return */ public static boolean isAuthDenied(int code) { return code == 401; } /** * Do a http operation * * @param urlConnection the HttpURLConnection to be used * @param inputData if not null,will be written to the urlconnection. * @param hasOutput if true, read content back from the urlconnection * @return Response * @throws IOException */ public static HttpResponse doOperation( HttpURLConnection urlConnection, byte[] inputData, boolean hasOutput) throws IOException { HttpResponse response = null; InputStream inputStream = null; OutputStream outputStream = null; byte[] payload = null; try { if (inputData != null) { urlConnection.setDoOutput(true); outputStream = urlConnection.getOutputStream(); outputStream.write(inputData); } /* * Get response code first. HttpURLConnection does not allow to * read inputstream if response code is not success code */ int responseCode = urlConnection.getResponseCode(); if (hasOutput && isSuccessCode(responseCode)) { payload = getBytes(urlConnection.getInputStream()); } response = new HttpResponse(urlConnection.getHeaderFields(), responseCode, payload); } finally { Util.close(inputStream); Util.close(outputStream); } return response; } /** * Get bytes of this {@link InputStream} * * @param inputStream * @return * @throws IOException */ public static byte[] getBytes(InputStream inputStream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int len; for (; ; ) { len = inputStream.read(buffer); if (len == -1) { break; } baos.write(buffer, 0, len); } return baos.toByteArray(); } /** * Do a operation that does not write or read from HttpURLConnection, except for the headers * * @param urlConnection the connection * @return Response * @throws IOException */ public static HttpResponse doVoidOperation(HttpURLConnection urlConnection) throws IOException { return doOperation(urlConnection, null, false); } /** * Do a operation that writes content to the HttpURLConnection * * @param urlConnection the connection * @param content The content to send * @return Response * @throws IOException */ public static HttpResponse doSendOperation(HttpURLConnection urlConnection, byte[] content) throws IOException { return doOperation(urlConnection, content, false); } /** * Do a operation that reads from the httpconnection * * @param urlConnection The connections * @return Response * @throws IOException */ public static HttpResponse doReadOperation(HttpURLConnection urlConnection) throws IOException { return doOperation(urlConnection, null, true); } } }
/** * Generic class to test one or more protocol layers directly. Sets up a protocol stack consisting * of the top layer (which is essentially given by the user and is the test harness), the specified * protocol(s) and a bottom layer (which is automatically added), which sends all received messages * immediately back up the stack (swapping sender and receiver address of the message). * * @author Bela Ban * @author March 23 2001 */ public class ProtocolTester { Protocol harness = null, top, bottom; String props = null; Configurator config = null; protected final Log log = LogFactory.getLog(this.getClass()); public ProtocolTester(String prot_spec, Protocol harness) throws Exception { if (prot_spec == null || harness == null) throw new Exception("ProtocolTester(): prot_spec or harness is null"); props = prot_spec; this.harness = harness; props = "LOOPBACK:" + props; // add a loopback layer at the bottom of the stack config = new Configurator(); JChannel mock_channel = new JChannel() {}; ProtocolStack stack = new ProtocolStack(mock_channel); stack.setup(Configurator.parseConfigurations(props)); stack.insertProtocol(harness, ProtocolStack.ABOVE, stack.getTopProtocol().getClass()); bottom = stack.getBottomProtocol(); // has to be set after StartProtocolStack, otherwise the up and down handler threads in the // harness // will be started as well (we don't want that) ! // top.setUpProtocol(harness); } public Vector<Protocol> getProtocols() { Vector<Protocol> retval = new Vector<Protocol>(); Protocol tmp = top; while (tmp != null) { retval.add(tmp); tmp = tmp.getDownProtocol(); } return retval; } public String getProtocolSpec() { return props; } public Protocol getBottom() { return bottom; } public Protocol getTop() { return top; } public void start() throws Exception { Protocol p; if (harness != null) { p = harness; while (p != null) { p.start(); p = p.getDownProtocol(); } } else if (top != null) { p = top; while (p != null) { p.start(); p = p.getDownProtocol(); } } } public void stop() { Protocol p; if (harness != null) { List<Protocol> protocols = new LinkedList<Protocol>(); p = harness; while (p != null) { protocols.add(p); p.stop(); p = p.getDownProtocol(); } p = harness; while (p != null) { p.destroy(); p = p.getDownProtocol(); } } else if (top != null) { p = top; List<Protocol> protocols = new LinkedList<Protocol>(); while (p != null) { protocols.add(p); p.stop(); p = p.getDownProtocol(); } p = top; while (p != null) { p.destroy(); p = p.getDownProtocol(); } } } private final Protocol getBottomProtocol(Protocol top) { Protocol tmp; if (top == null) return null; tmp = top; while (tmp.getDownProtocol() != null) tmp = tmp.getDownProtocol(); return tmp; } public static void main(String[] args) { String props; ProtocolTester t; Harness h; if (args.length < 1 || args.length > 2) { System.out.println("ProtocolTester <protocol stack spec> [-trace]"); return; } props = args[0]; try { h = new Harness(); t = new ProtocolTester(props, h); System.out.println("protocol specification is " + t.getProtocolSpec()); h.down(new Event(Event.BECOME_SERVER)); for (int i = 0; i < 5; i++) { System.out.println("Sending msg #" + i); h.down(new Event(Event.MSG, new Message(null, null, "Hello world #" + i))); } Util.sleep(500); t.stop(); } catch (Exception ex) { System.err.println(ex); } } private static class Harness extends Protocol { public String getName() { return "Harness"; } public Object up(Event evt) { System.out.println("Harness.up(): " + evt); return null; } } }
/** @author Bela Ban */ public class GridInputStream extends InputStream { final Cache<String, byte[]> cache; final int chunk_size; final String name; protected final GridFile file; // file representing this input stream int index = 0; // index into the file for writing int local_index = 0; byte[] current_buffer = null; boolean end_reached = false; static final Log log = LogFactory.getLog(GridInputStream.class); GridInputStream(GridFile file, Cache<String, byte[]> cache, int chunk_size) throws FileNotFoundException { this.file = file; this.name = file.getPath(); this.cache = cache; this.chunk_size = chunk_size; } public int read() throws IOException { int bytes_remaining_to_read = getBytesRemainingInChunk(); if (bytes_remaining_to_read == 0) { if (end_reached) return -1; current_buffer = fetchNextChunk(); local_index = 0; if (current_buffer == null) return -1; else if (current_buffer.length < chunk_size) end_reached = true; bytes_remaining_to_read = getBytesRemainingInChunk(); } int retval = current_buffer[local_index++]; index++; return retval; } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(byte[] b, int off, int len) throws IOException { int bytes_read = 0; while (len > 0) { int bytes_remaining_to_read = getBytesRemainingInChunk(); if (bytes_remaining_to_read == 0) { if (end_reached) return bytes_read > 0 ? bytes_read : -1; current_buffer = fetchNextChunk(); local_index = 0; if (current_buffer == null) return bytes_read > 0 ? bytes_read : -1; else if (current_buffer.length < chunk_size) end_reached = true; bytes_remaining_to_read = getBytesRemainingInChunk(); } int bytes_to_read = Math.min(len, bytes_remaining_to_read); // bytes_to_read=Math.min(bytes_to_read, current_buffer.length - local_index); System.arraycopy(current_buffer, local_index, b, off, bytes_to_read); local_index += bytes_to_read; off += bytes_to_read; len -= bytes_to_read; bytes_read += bytes_to_read; index += bytes_to_read; } return bytes_read; } @Override public long skip(long n) throws IOException { throw new UnsupportedOperationException(); } @Override public int available() throws IOException { throw new UnsupportedOperationException(); } @Override public void close() throws IOException { local_index = index = 0; end_reached = false; } private int getBytesRemainingInChunk() { // return chunk_size - local_index; return current_buffer == null ? 0 : current_buffer.length - local_index; } private byte[] fetchNextChunk() { int chunk_number = getChunkNumber(); String key = name + ".#" + chunk_number; byte[] val = cache.get(key); if (log.isTraceEnabled()) log.trace( "fetching index=" + index + ", key=" + key + ": " + (val != null ? val.length + " bytes" : "null")); return val; } private int getChunkNumber() { return index / chunk_size; } }
public class XmlConfigurator implements ProtocolStackConfigurator { private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; private final List<ProtocolConfiguration> configuration = new ArrayList<ProtocolConfiguration>(); protected static final Log log = LogFactory.getLog(XmlConfigurator.class); protected XmlConfigurator(List<ProtocolConfiguration> protocols) { configuration.addAll(protocols); } public static XmlConfigurator getInstance(URL url) throws java.io.IOException { return getInstance(url, null); } public static XmlConfigurator getInstance(InputStream stream) throws java.io.IOException { return getInstance(stream, null); } public static XmlConfigurator getInstance(Element el) throws java.io.IOException { return parse(el); } public static XmlConfigurator getInstance(URL url, Boolean validate) throws java.io.IOException { InputStream is = url.openStream(); try { return getInstance(is, validate); } finally { Util.close(is); } } public static XmlConfigurator getInstance(InputStream stream, Boolean validate) throws java.io.IOException { return parse(stream, validate); } /** * @param convert If false: print old plain output, else print new XML format * @return String with protocol stack in specified format */ public String getProtocolStackString(boolean convert) { StringBuilder buf = new StringBuilder(); Iterator<ProtocolConfiguration> it = configuration.iterator(); if (convert) buf.append("<config>\n"); while (it.hasNext()) { ProtocolConfiguration d = it.next(); if (convert) buf.append(" <"); buf.append(d.getProtocolString(convert)); if (convert) buf.append("/>"); if (it.hasNext()) { if (convert) buf.append('\n'); else buf.append(':'); } } if (convert) buf.append("\n</config>"); return buf.toString(); } public String getProtocolStackString() { return getProtocolStackString(false); } public List<ProtocolConfiguration> getProtocolStack() { return configuration; } protected static XmlConfigurator parse(InputStream stream, Boolean validate) throws java.io.IOException { /** * CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the code below is pretty * amateurish... But it seems to work, and it is executed only on startup, so no perf loss on * the critical path. If somebody wants to improve this, please be my guest. */ try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); boolean validation = false; String tmp = Util.getProperty(new String[] {Global.XML_VALIDATION}, null, null, false, null); if (tmp != null) { validation = Boolean.valueOf(tmp).booleanValue(); } else if (validate != null) { validation = validate.booleanValue(); } factory.setValidating(validation); factory.setNamespaceAware(validation); if (validation) { factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); } DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver( new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) throws IOException { if (systemId != null && systemId.startsWith("http://www.jgroups.org/schema/JGroups-")) { String schemaName = systemId.substring("http://www.jgroups.org/".length()); InputStream schemaIs = getAsInputStreamFromClassLoader(schemaName); if (schemaIs == null) { throw new IOException("Schema not found from classloader: " + schemaName); } InputSource source = new InputSource(schemaIs); source.setPublicId(publicId); source.setSystemId(systemId); return source; } return null; } }); // Use AtomicReference to allow make variable final, not for atomicity // We store only last exception final AtomicReference<SAXParseException> exceptionRef = new AtomicReference<SAXParseException>(); builder.setErrorHandler( new ErrorHandler() { public void warning(SAXParseException exception) throws SAXException { log.warn("Warning during parse", exception); } public void fatalError(SAXParseException exception) throws SAXException { exceptionRef.set(exception); } public void error(SAXParseException exception) throws SAXException { exceptionRef.set(exception); } }); Document document = builder.parse(stream); if (exceptionRef.get() != null) { throw exceptionRef.get(); } // The root element of the document should be the "config" element, // but the parser(Element) method checks this so a check is not // needed here. Element configElement = document.getDocumentElement(); return parse(configElement); } catch (Exception x) { throw new IOException(Util.getMessage("ParseError", x.getLocalizedMessage())); } } private static InputStream getAsInputStreamFromClassLoader(String filename) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); InputStream is = cl == null ? null : cl.getResourceAsStream(filename); if (is == null) { // check system class loader is = XmlConfigurator.class.getClassLoader().getResourceAsStream(filename); } return is; } protected static XmlConfigurator parse(Element root_element) throws java.io.IOException { XmlConfigurator configurator = null; final LinkedList<ProtocolConfiguration> prot_data = new LinkedList<ProtocolConfiguration>(); /** * CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the code below is pretty * amateurish... But it seems to work, and it is executed only on startup, so no perf loss on * the critical path. If somebody wants to improve this, please be my guest. */ try { String root_name = root_element.getNodeName(); if (!"config".equals(root_name.trim().toLowerCase())) throw new IOException("the configuration does not start with a <config> element"); NodeList prots = root_element.getChildNodes(); for (int i = 0; i < prots.getLength(); i++) { Node node = prots.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) continue; Element tag = (Element) node; String protocol = tag.getTagName(); Map<String, String> params = new HashMap<String, String>(); NamedNodeMap attrs = tag.getAttributes(); int attrLength = attrs.getLength(); for (int a = 0; a < attrLength; a++) { Attr attr = (Attr) attrs.item(a); String name = attr.getName(); String value = attr.getValue(); params.put(name, value); } ProtocolConfiguration cfg = new ProtocolConfiguration(protocol, params); prot_data.add(cfg); } configurator = new XmlConfigurator(prot_data); } catch (Exception x) { if (x instanceof java.io.IOException) throw (java.io.IOException) x; else { IOException tmp = new IOException(); tmp.initCause(x); throw tmp; } } return configurator; } public static void main(String[] args) throws Exception { String input_file = null; XmlConfigurator conf; boolean old_format = false; for (int i = 0; i < args.length; i++) { if (args[i].equals("-old")) { old_format = true; continue; } if (args[i].equals("-file")) { input_file = args[++i]; continue; } help(); return; } if (input_file != null) { InputStream input = null; try { input = new FileInputStream(new File(input_file)); } catch (Throwable t) { } if (input == null) { try { input = new URL(input_file).openStream(); } catch (Throwable t) { } } if (input == null) input = Thread.currentThread().getContextClassLoader().getResourceAsStream(input_file); if (old_format) { String cfg = inputAsString(input); List<ProtocolConfiguration> tmp = Configurator.parseConfigurations(cfg); System.out.println(dump(tmp)); } else { conf = XmlConfigurator.getInstance(input); String tmp = conf.getProtocolStackString(); System.out.println("\n" + tmp); } } else { log.error("no input file given"); } } private static String dump(Collection<ProtocolConfiguration> configs) { StringBuilder sb = new StringBuilder(); String indent = " "; sb.append("<config>\n"); for (ProtocolConfiguration cfg : configs) { sb.append(indent).append("<").append(cfg.getProtocolName()); Map<String, String> props = cfg.getProperties(); if (props.isEmpty()) { sb.append(" />\n"); } else { sb.append("\n").append(indent).append(indent); for (Map.Entry<String, String> entry : props.entrySet()) { String key = entry.getKey(); String val = entry.getValue(); key = trim(key); val = trim(val); sb.append(key).append("=\"").append(val).append("\" "); } sb.append(" />\n"); } } sb.append("</config>\n"); return sb.toString(); } private static String trim(String val) { String retval = ""; int index; val = val.trim(); while (true) { index = val.indexOf('\n'); if (index == -1) { retval += val; break; } retval += val.substring(0, index); val = val.substring(index + 1); } return retval; } private static String inputAsString(InputStream input) throws IOException { int len = input.available(); byte[] buf = new byte[len]; input.read(buf, 0, len); return new String(buf); } public static String replace(String input, final String expr, String replacement) { StringBuilder sb = new StringBuilder(); int new_index = 0, index = 0, len = expr.length(), input_len = input.length(); while (true) { new_index = input.indexOf(expr, index); if (new_index == -1) { sb.append(input.substring(index, input_len)); break; } sb.append(input.substring(index, new_index)); sb.append(replacement); index = new_index + len; } return sb.toString(); } static void help() { System.out.println("XmlConfigurator -file <input XML file> [-old]"); System.out.println("(-old: converts old (plain-text) input format into new XML format)"); } }
/** * Handles merging. Called by CoordGmsImpl and ParticipantGmsImpl * * @author Bela Ban */ public class Merger { private final GMS gms; private final Log log = LogFactory.getLog(getClass()); private final MergeTask merge_task = new MergeTask(); /** For MERGE_REQ/MERGE_RSP correlation, contains MergeData elements */ private final ResponseCollector<MergeData> merge_rsps = new ResponseCollector<MergeData>(); /** For GET_DIGEST / DIGEST_RSP correlation */ private final ResponseCollector<Digest> digest_collector = new ResponseCollector<Digest>(); /** To serialize access to merge_id */ private final Lock merge_lock = new ReentrantLock(); @GuardedBy("merge_lock") private MergeId merge_id = null; protected final BoundedList<MergeId> merge_id_history = new BoundedList<MergeId>(20); @GuardedBy("merge_killer_lock") private Future<?> merge_killer = null; private final Lock merge_killer_lock = new ReentrantLock(); public Merger(GMS gms) { this.gms = gms; } public String getMergeIdAsString() { return merge_id != null ? merge_id.toString() : null; } public String getMergeIdHistory() { return merge_id_history.toString(); } /** * Invoked upon receiving a MERGE event from the MERGE layer. Starts the merge protocol. See * description of protocol in DESIGN. * * @param views A List of <em>different</em> views detected by the merge protocol */ public void merge(Map<Address, View> views) { if (isMergeInProgress()) { if (log.isTraceEnabled()) log.trace(gms.local_addr + ": merge is already running (merge_id=" + merge_id + ")"); return; } // we need the merge *coordinators* not merge participants because not everyone can lead a merge // ! Collection<Address> coords = Util.determineActualMergeCoords(views); if (coords.isEmpty()) { log.error( gms.local_addr + ": unable to determine merge leader from " + views + "; not starting a merge"); return; } Membership tmp = new Membership(coords); // establish a deterministic order, so that coords can elect leader tmp.sort(); Address merge_leader = tmp.elementAt(0); if (merge_leader.equals(gms.local_addr)) { if (log.isDebugEnabled()) { Collection<Address> merge_participants = Util.determineMergeParticipants(views); log.debug( "I (" + gms.local_addr + ") will be the leader. Starting the merge task for " + merge_participants.size() + " coords"); } merge_task.start(views); } else { if (log.isTraceEnabled()) log.trace( "I (" + gms.local_addr + ") am not the merge leader, " + "waiting for merge leader (" + merge_leader + ") to initiate merge"); } } /** * Get the view and digest and send back both (MergeData) in the form of a MERGE_RSP to the * sender. If a merge is already in progress, send back a MergeData with the merge_rejected field * set to true. * * @param sender The address of the merge leader * @param merge_id The merge ID * @param mbrs The set of members from which we expect responses */ public void handleMergeRequest( Address sender, MergeId merge_id, Collection<? extends Address> mbrs) { try { _handleMergeRequest(sender, merge_id, mbrs); } catch (Throwable t) { cancelMerge(merge_id); sendMergeRejectedResponse(sender, merge_id); } } protected void _handleMergeRequest( Address sender, MergeId merge_id, Collection<? extends Address> mbrs) throws Exception { boolean success = matchMergeId(merge_id) || setMergeId(null, merge_id); if (!success) throw new Exception( "merge " + this.merge_id + " is already in progress, received merge-id=" + merge_id); /* Clears the view handler queue and discards all JOIN/LEAVE/MERGE requests until after the MERGE */ // gms.getViewHandler().suspend(); if (log.isTraceEnabled()) log.trace( gms.local_addr + ": got merge request from " + sender + ", merge_id=" + merge_id + ", mbrs=" + mbrs); // merge the membership of the current view with mbrs List<Address> members = new LinkedList<Address>(); if (mbrs != null) { // didn't use a set because we didn't want to change the membership order at this // time (although not incorrect) for (Address mbr : mbrs) { if (!members.contains(mbr)) members.add(mbr); } } ViewId tmp_vid = gms.getViewId(); if (tmp_vid != null) tmp_vid = tmp_vid.copy(); if (tmp_vid == null) throw new Exception("view ID is null; cannot return merge response"); View view = new View(tmp_vid, new ArrayList<Address>(members)); // [JGRP-524] - FLUSH and merge: flush doesn't wrap entire merge process // [JGRP-770] - Concurrent startup of many channels doesn't stabilize // [JGRP-700] - FLUSH: flushing should span merge /*if flush is in stack, let this coordinator flush its cluster island */ if (gms.flushProtocolInStack && !gms.startFlush(view)) throw new Exception("flush failed"); // we still need to fetch digests from all members, and not just return our own digest // (https://issues.jboss.org/browse/JGRP-948) Digest digest = fetchDigestsFromAllMembersInSubPartition(members, merge_id); if (digest == null || digest.size() == 0) throw new Exception( "failed fetching digests from subpartition members; dropping merge response"); sendMergeResponse(sender, view, digest, merge_id); } public void handleMergeResponse(MergeData data, MergeId merge_id) { if (!matchMergeId(merge_id)) { if (log.isTraceEnabled()) log.trace( gms.local_addr + ": this.merge_id (" + this.merge_id + ") is different from merge_id " + merge_id + " sent by " + data.getSender() + " as merge response, discarding it"); return; } merge_rsps.add(data.getSender(), data); } /** * If merge_id is not equal to this.merge_id then discard. Else cast the view/digest to all * members of this group. */ public void handleMergeView(final MergeData data, final MergeId merge_id) { if (!matchMergeId(merge_id)) { if (log.isTraceEnabled()) log.trace( gms.local_addr + ": merge_ids (mine: " + this.merge_id + ", received: " + merge_id + ") don't match; merge view " + data.view.getViewId() + " is discarded"); return; } // only send to our *current* members, if we have A and B being merged (we are B), then we would // *not* // want to block on a VIEW_ACK from A because A doesn't see us in the pre-merge view yet and // discards the view List<Address> newViewMembers = new ArrayList<Address>(data.view.getMembers()); newViewMembers.removeAll(gms.members.getMembers()); try { gms.castViewChange(data.view, data.digest, null, newViewMembers); // if we have flush in stack send ack back to merge coordinator if (gms.flushProtocolInStack) { // [JGRP-700] - FLUSH: flushing should span merge Message ack = new Message(data.getSender()).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL); GMS.GmsHeader ack_hdr = new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW_OK); ack.putHeader(gms.getId(), ack_hdr); gms.getDownProtocol().down(new Event(Event.MSG, ack)); } } finally { cancelMerge(merge_id); } } public void handleMergeCancelled(MergeId merge_id) { try { gms.stopFlush(); } catch (Throwable t) { log.error("stop flush failed", t); } if (log.isTraceEnabled()) log.trace(gms.local_addr + ": merge " + merge_id + " is cancelled"); cancelMerge(merge_id); } public void handleDigestResponse(Address sender, Digest digest) { digest_collector.add(sender, digest); } /** * Removes all members from a given view which don't have us in their view * (https://jira.jboss.org/browse/JGRP-1061). Example: * * <pre> * A: AB * B: AB * C: ABC * </pre> * * becomes * * <pre> * A: AB * B: AB * C: C // A and B don't have C in their views * </pre> * * @param map A map of members and their associated views */ public static void sanitizeViews(Map<Address, View> map) { if (map == null) return; for (Map.Entry<Address, View> entry : map.entrySet()) { Address key = entry.getKey(); List<Address> members = new ArrayList<Address>(entry.getValue().getMembers()); boolean modified = false; for (Iterator<Address> it = members.iterator(); it.hasNext(); ) { Address val = it.next(); if (val.equals(key)) // we can always talk to ourself ! continue; View view = map.get(val); final Collection<Address> tmp_mbrs = view != null ? view.getMembers() : null; if (tmp_mbrs != null && !tmp_mbrs.contains(key)) { it.remove(); modified = true; } } if (modified) { View old_view = entry.getValue(); entry.setValue(new View(old_view.getVid(), members)); } } } /** Send back a response containing view and digest to sender */ private void sendMergeResponse(Address sender, View view, Digest digest, MergeId merge_id) { Message msg = new Message(sender).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL); GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP); hdr.merge_id = merge_id; hdr.view = view; hdr.my_digest = digest; msg.putHeader(gms.getId(), hdr); if (log.isTraceEnabled()) log.trace(gms.local_addr + ": sending merge response=" + hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } /** * Sends the new view and digest to all subgroup coordinors in coords. Each coord will in turn * * <ol> * <li>broadcast the new view and digest to all the members of its subgroup (MergeView) * <li>on reception of the view, if it is a MergeView, each member will set the digest and * install the new view * </ol> */ private void sendMergeView( Collection<Address> coords, MergeData combined_merge_data, MergeId merge_id) { if (coords == null || combined_merge_data == null) return; View view = combined_merge_data.view; Digest digest = combined_merge_data.digest; if (view == null || digest == null) { if (log.isErrorEnabled()) log.error("view or digest is null, cannot send consolidated merge view/digest"); return; } int size = 0; if (gms.flushProtocolInStack) { gms.merge_ack_collector.reset(coords); size = gms.merge_ack_collector.size(); } long start = System.currentTimeMillis(); for (Address coord : coords) { Message msg = new Message(coord); GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW); hdr.view = view; hdr.my_digest = digest; hdr.merge_id = merge_id; msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } // [JGRP-700] - FLUSH: flushing should span merge // if flush is in stack wait for acks from separated island coordinators if (gms.flushProtocolInStack) { try { gms.merge_ack_collector.waitForAllAcks(gms.view_ack_collection_timeout); if (log.isTraceEnabled()) log.trace( gms.local_addr + ": received all ACKs (" + size + ") for merge view " + view + " in " + (System.currentTimeMillis() - start) + "ms"); } catch (TimeoutException e) { log.warn( gms.local_addr + ": failed to collect all ACKs (" + size + ") for merge view " + view + " after " + gms.view_ack_collection_timeout + "ms, missing ACKs from " + gms.merge_ack_collector.printMissing()); } } } protected void sendMergeRejectedResponse(Address sender, MergeId merge_id) { Message msg = new Message(sender).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL); GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP); hdr.merge_rejected = true; hdr.merge_id = merge_id; msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } private void sendMergeCancelledMessage(Collection<Address> coords, MergeId merge_id) { if (coords == null || merge_id == null) return; for (Address coord : coords) { Message msg = new Message(coord); // msg.setFlag(Message.Flag.OOB); GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.CANCEL_MERGE); hdr.merge_id = merge_id; msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } } /** * Multicasts a GET_DIGEST_REQ to all current members and waits for all responses (GET_DIGEST_RSP) * or N ms. * * @return */ private Digest fetchDigestsFromAllMembersInSubPartition( List<Address> current_mbrs, MergeId merge_id) { // Optimization: if we're the only member, we don't need to multicast the get-digest message if (current_mbrs == null || current_mbrs.size() == 1 && current_mbrs.get(0).equals(gms.local_addr)) return (Digest) gms.getDownProtocol().down(new Event(Event.GET_DIGEST, gms.local_addr)); GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.GET_DIGEST_REQ); hdr.merge_id = merge_id; Message get_digest_req = new Message().setFlag(Message.Flag.OOB, Message.Flag.INTERNAL).putHeader(gms.getId(), hdr); long max_wait_time = gms.merge_timeout / 2; // gms.merge_timeout is guaranteed to be > 0, verified in init() digest_collector.reset(current_mbrs); gms.getDownProtocol().down(new Event(Event.MSG, get_digest_req)); // add my own digest first - the get_digest_req needs to be sent first *before* getting our own // digest, so // we have that message in our digest ! Digest digest = (Digest) gms.getDownProtocol().down(new Event(Event.GET_DIGEST, gms.local_addr)); digest_collector.add(gms.local_addr, digest); digest_collector.waitForAllResponses(max_wait_time); if (log.isTraceEnabled()) { if (digest_collector.hasAllResponses()) log.trace(gms.local_addr + ": fetched all digests for " + current_mbrs); else log.trace( gms.local_addr + ": fetched incomplete digests (after timeout of " + max_wait_time + ") ms for " + current_mbrs); } Map<Address, Digest> responses = new HashMap<Address, Digest>(digest_collector.getResults()); MutableDigest retval = new MutableDigest(responses.size()); for (Digest dig : responses.values()) { if (dig != null) retval.add(dig); } return retval; } /** * Fetches the digests from all members and installs them again. Used only for diagnosis and * support; don't use this otherwise ! */ void fixDigests() { Digest digest = fetchDigestsFromAllMembersInSubPartition(gms.view.getMembers(), null); Message msg = new Message(); GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.INSTALL_DIGEST); hdr.my_digest = digest; msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } void stop() { merge_task.stop(); } void cancelMerge(MergeId id) { if (setMergeId(id, null)) { merge_task.stop(); stopMergeKiller(); merge_rsps.reset(); gms.getViewHandler().resume(); gms.getDownProtocol().down(new Event(Event.RESUME_STABLE)); } } boolean isMergeTaskRunning() { return merge_task.isRunning(); } boolean isMergeKillerTaskRunning() { return merge_killer != null && !merge_killer.isDone(); } void forceCancelMerge() { merge_lock.lock(); try { if (this.merge_id != null) cancelMerge(this.merge_id); } finally { merge_lock.unlock(); } } public boolean setMergeId(MergeId expected, MergeId new_value) { merge_lock.lock(); try { boolean match = Util.match(this.merge_id, expected); if (match) { if (new_value != null && merge_id_history.contains(new_value)) return false; else merge_id_history.add(new_value); this.merge_id = new_value; if (this.merge_id != null) { // Clears the view handler queue and discards all JOIN/LEAVE/MERGE requests until after // the MERGE gms.getViewHandler().suspend(); gms.getDownProtocol().down(new Event(Event.SUSPEND_STABLE, 20000)); startMergeKiller(); } } return match; } finally { merge_lock.unlock(); } } /** Only used for testing, might get removed any time. Do not use ! */ public MergeId getMergeId() { merge_lock.lock(); try { return merge_id; } finally { merge_lock.unlock(); } } public boolean isMergeInProgress() { merge_lock.lock(); try { return merge_id != null; } finally { merge_lock.unlock(); } } public boolean matchMergeId(MergeId id) { merge_lock.lock(); try { return Util.match(this.merge_id, id); } finally { merge_lock.unlock(); } } private void startMergeKiller() { merge_killer_lock.lock(); try { if (merge_killer == null || merge_killer.isDone()) { MergeKiller task = new MergeKiller(this.merge_id); merge_killer = gms.timer.schedule(task, (long) (gms.merge_timeout * 2), TimeUnit.MILLISECONDS); } } finally { merge_killer_lock.unlock(); } } private void stopMergeKiller() { merge_killer_lock.lock(); try { if (merge_killer != null) { merge_killer.cancel(false); merge_killer = null; } } catch (Throwable t) { } finally { merge_killer_lock.unlock(); } } /** * Starts the merge protocol (only run by the merge leader). Essentially sends a MERGE_REQ to all * coordinators of all subgroups found. Each coord receives its digest and view and returns it. * The leader then computes the digest and view for the new group from the return values. Finally, * it sends this merged view/digest to all subgroup coordinators; each coordinator will install it * in their subgroup. */ class MergeTask implements Runnable { private Thread thread = null; /** List of all subpartition coordinators and their members */ private final ConcurrentMap<Address, Collection<Address>> coords = Util.createConcurrentMap(8, 0.75f, 8); /** * @param views Guaranteed to be non-null and to have >= 2 members, or else this thread would * not be started */ public synchronized void start(Map<Address, View> views) { if (thread != null && thread.isAlive()) // the merge thread is already running return; this.coords.clear(); // now remove all members which don't have us in their view, so RPCs won't block (e.g. FLUSH) // https://jira.jboss.org/browse/JGRP-1061 sanitizeViews(views); // Add all different coordinators of the views into the hashmap and sets their members: Collection<Address> coordinators = Util.determineMergeCoords(views); for (Address coord : coordinators) { View view = views.get(coord); if (view != null) this.coords.put(coord, new ArrayList<Address>(view.getMembers())); } // For the merge participants which are not coordinator, we simply add them, and the // associated // membership list consists only of themselves Collection<Address> merge_participants = Util.determineMergeParticipants(views); merge_participants.removeAll(coordinators); for (Address merge_participant : merge_participants) { Collection<Address> tmp = new ArrayList<Address>(); tmp.add(merge_participant); coords.putIfAbsent(merge_participant, tmp); } thread = gms.getThreadFactory().newThread(this, "MergeTask"); thread.setDaemon(true); thread.start(); } public synchronized void stop() { Thread tmp = thread; if (thread != null && thread.isAlive()) tmp.interrupt(); thread = null; } public synchronized boolean isRunning() { return thread != null && thread.isAlive(); } public void run() { // 1. Generate merge_id final MergeId new_merge_id = MergeId.create(gms.local_addr); final Collection<Address> coordsCopy = new ArrayList<Address>(coords.keySet()); long start = System.currentTimeMillis(); try { _run(new_merge_id, coordsCopy); // might remove members from coordsCopy } catch (Throwable ex) { if (log.isWarnEnabled()) log.warn(gms.local_addr + ": " + ex + ", merge is cancelled"); sendMergeCancelledMessage(coordsCopy, new_merge_id); cancelMerge( new_merge_id); // the message above cancels the merge, too, but this is a 2nd line of // defense } finally { /* 5. if flush is in stack stop the flush for entire cluster [JGRP-700] - FLUSH: flushing should span merge */ if (gms.flushProtocolInStack) gms.stopFlush(); thread = null; } long diff = System.currentTimeMillis() - start; if (log.isDebugEnabled()) log.debug(gms.local_addr + ": merge " + new_merge_id + " took " + diff + " ms"); } /** Runs the merge protocol as a leader */ protected void _run(MergeId new_merge_id, final Collection<Address> coordsCopy) throws Exception { boolean success = setMergeId(null, new_merge_id); if (!success) { log.warn("failed to set my own merge_id (" + merge_id + ") to " + new_merge_id); return; } if (log.isDebugEnabled()) log.debug( gms.local_addr + ": merge task " + merge_id + " started with " + coords.keySet().size() + " coords"); /* 2. Fetch the current Views/Digests from all subgroup coordinators */ success = getMergeDataFromSubgroupCoordinators(coords, new_merge_id, gms.merge_timeout); List<Address> missing = null; if (!success) { missing = merge_rsps.getMissing(); if (log.isDebugEnabled()) log.debug( "merge leader " + gms.local_addr + " did not get responses from all " + coords.keySet().size() + " partition coordinators; missing responses from " + missing.size() + " members, removing them from the merge"); merge_rsps.remove(missing); } /* 3. Remove null or rejected merge responses from merge_rsp and coords (so we'll send the new view * only to members who accepted the merge request) */ if (missing != null && !missing.isEmpty()) { coords.keySet().removeAll(missing); coordsCopy.removeAll(missing); } removeRejectedMergeRequests(coords.keySet()); if (merge_rsps.size() == 0) throw new Exception("did not get any merge responses from partition coordinators"); if (!coords .keySet() .contains( gms.local_addr)) // another member might have invoked a merge req on us before we got // there... throw new Exception("merge leader rejected merge request"); /* 4. Combine all views and digests into 1 View/1 Digest */ List<MergeData> merge_data = new ArrayList<MergeData>(merge_rsps.getResults().values()); MergeData combined_merge_data = consolidateMergeData(merge_data); if (combined_merge_data == null) throw new Exception("could not consolidate merge"); /* 4. Send the new View/Digest to all coordinators (including myself). On reception, they will install the digest and view in all of their subgroup members */ if (log.isDebugEnabled()) log.debug( gms.local_addr + ": installing merge view " + combined_merge_data.view.getViewId() + " (" + combined_merge_data.view.size() + " members) in " + coords.keySet().size() + " coords"); sendMergeView(coords.keySet(), combined_merge_data, new_merge_id); } /** * Sends a MERGE_REQ to all coords and populates a list of MergeData (in merge_rsps). Returns * after coords.size() response have been received, or timeout msecs have elapsed (whichever is * first). * * <p>If a subgroup coordinator rejects the MERGE_REQ (e.g. because of participation in a * different merge), <em>that member will be removed from coords !</em> * * @param coords A map of coordinatgor addresses and associated membership lists * @param new_merge_id The new merge id * @param timeout Max number of msecs to wait for the merge responses from the subgroup coords */ protected boolean getMergeDataFromSubgroupCoordinators( Map<Address, Collection<Address>> coords, MergeId new_merge_id, long timeout) { boolean gotAllResponses; long start = System.currentTimeMillis(); merge_rsps.reset(coords.keySet()); if (log.isTraceEnabled()) log.trace(gms.local_addr + ": sending MERGE_REQ to " + coords.keySet()); for (Map.Entry<Address, Collection<Address>> entry : coords.entrySet()) { Address coord = entry.getKey(); Collection<Address> mbrs = entry.getValue(); Message msg = new Message(coord).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL); GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.MERGE_REQ, mbrs); hdr.mbr = gms.local_addr; hdr.merge_id = new_merge_id; msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } // wait until num_rsps_expected >= num_rsps or timeout elapsed merge_rsps.waitForAllResponses(timeout); gotAllResponses = merge_rsps.hasAllResponses(); long stop = System.currentTimeMillis(); if (log.isTraceEnabled()) log.trace( gms.local_addr + ": collected " + merge_rsps.numberOfValidResponses() + " merge response(s) in " + (stop - start) + " ms"); return gotAllResponses; } /** * Removed rejected merge requests from merge_rsps and coords. This method has a lock on * merge_rsps */ private void removeRejectedMergeRequests(Collection<Address> coords) { int num_removed = 0; for (Iterator<Map.Entry<Address, MergeData>> it = merge_rsps.getResults().entrySet().iterator(); it.hasNext(); ) { Map.Entry<Address, MergeData> entry = it.next(); MergeData data = entry.getValue(); if (data.merge_rejected) { if (data.getSender() != null) coords.remove(data.getSender()); it.remove(); num_removed++; } } if (num_removed > 0) { if (log.isTraceEnabled()) log.trace(gms.local_addr + ": removed " + num_removed + " rejected merge responses"); } } /** * Merge all MergeData. All MergeData elements should be disjunct (both views and digests). * However, this method is prepared to resolve duplicate entries (for the same member). * Resolution strategy for views is to merge only 1 of the duplicate members. Resolution * strategy for digests is to take the higher seqnos for duplicate digests. * * <p>After merging all members into a Membership and subsequent sorting, the first member of * the sorted membership will be the new coordinator. This method has a lock on merge_rsps. * * @param merge_rsps A list of MergeData items. Elements with merge_rejected=true were removed * before. Is guaranteed not to be null and to contain at least 1 member. */ private MergeData consolidateMergeData(List<MergeData> merge_rsps) { long logical_time = 0; // for new_vid List<View> subgroups = new ArrayList<View>(11); // contains a list of Views, each View is a subgroup Collection<Collection<Address>> sub_mbrships = new ArrayList<Collection<Address>>(); for (MergeData tmp_data : merge_rsps) { View tmp_view = tmp_data.getView(); if (tmp_view != null) { ViewId tmp_vid = tmp_view.getVid(); if (tmp_vid != null) { // compute the new view id (max of all vids +1) logical_time = Math.max(logical_time, tmp_vid.getId()); } // merge all membership lists into one (prevent duplicates) sub_mbrships.add(new ArrayList<Address>(tmp_view.getMembers())); subgroups.add(tmp_view.copy()); } } // determine the new digest Digest new_digest = consolidateDigests(merge_rsps, merge_rsps.size()); if (new_digest == null) return null; // remove all members from the new member list that are not in the digest Collection<Address> digest_mbrs = new_digest.getMembers(); for (Collection<Address> coll : sub_mbrships) coll.retainAll(digest_mbrs); List<Address> merged_mbrs = gms.computeNewMembership(sub_mbrships); // the new coordinator is the first member of the consolidated & sorted membership list Address new_coord = merged_mbrs.isEmpty() ? null : merged_mbrs.get(0); if (new_coord == null) return null; // should be the highest view ID seen up to now plus 1 ViewId new_vid = new ViewId(new_coord, logical_time + 1); // determine the new view MergeView new_view = new MergeView(new_vid, merged_mbrs, subgroups); if (log.isTraceEnabled()) log.trace( gms.local_addr + ": consolidated view=" + new_view + "\nconsolidated digest=" + new_digest); return new MergeData(gms.local_addr, new_view, new_digest); } /** * Merge all digests into one. For each sender, the new value is max(highest_delivered), * max(highest_received). This method has a lock on merge_rsps */ private Digest consolidateDigests(List<MergeData> merge_rsps, int num_mbrs) { MutableDigest retval = new MutableDigest(num_mbrs); for (MergeData data : merge_rsps) { Digest tmp_digest = data.getDigest(); if (tmp_digest == null) continue; retval.merge(tmp_digest); } return retval.copy(); } } private class MergeKiller implements Runnable { private final MergeId my_merge_id; MergeKiller(MergeId my_merge_id) { this.my_merge_id = my_merge_id; } public void run() { cancelMerge(my_merge_id); } public String toString() { return Merger.class.getSimpleName() + ": " + getClass().getSimpleName(); } } }
/** * This class is to be used to pick up execution requests and actually run them. A single instance * can be used across any number of threads. * * @author wburns */ public class ExecutionRunner implements Runnable { protected JChannel ch; protected Executing _execProt; public ExecutionRunner(JChannel channel) { setChannel(channel); } public void setChannel(JChannel ch) { this.ch = ch; _execProt = (Executing) ch.getProtocolStack().findProtocol(Executing.class); if (_execProt == null) throw new IllegalStateException( "Channel configuration must include a executing protocol " + "(subclass of " + Executing.class.getName() + ")"); } protected static class Holder<T> { protected T value; public Holder(T value) { this.value = value; } } // @see java.lang.Runnable#run() @Override public void run() { final Lock shutdownLock = new ReentrantLock(); // The following 2 atomic boolean should only ever be updated while // protected by the above lock. They don't have to be atomic boolean, // but it is a nice wrapper to share a reference between threads. // Reads can be okay in certain circumstances final AtomicBoolean canInterrupt = new AtomicBoolean(true); final AtomicBoolean shutdown = new AtomicBoolean(); // This thread is only spawned so that we can differentiate between // an interrupt of a task and an interrupt causing a shutdown of // runner itself. Thread executionThread = new Thread() { // @see java.lang.Thread#run() @Override public void run() { Thread currentThread = Thread.currentThread(); Runnable runnable = null; // This task exits by being interrupted when the task isn't running // or by updating shutdown to true when it can't be interrupted while (!shutdown.get()) { _runnables.put(currentThread, new Holder<>(null)); runnable = (Runnable) ch.down(new ExecutorEvent(ExecutorEvent.CONSUMER_READY, null)); // This means we were interrupted while waiting if (runnable == null) break; // First retrieve the lock to make sure we can tell them // to not interrupt us shutdownLock.lock(); try { // Clear interrupt state as we don't want to stop the // task we just received. If we got a shutdown signal // we will only do it after we loop back around. Thread.interrupted(); canInterrupt.set(false); } finally { shutdownLock.unlock(); } _runnables.put(currentThread, new Holder<>(runnable)); Throwable throwable = null; try { runnable.run(); } // This can only happen if user is directly doing an execute(Runnable) catch (Throwable t) { _logger.error("Unexpected Runtime Error encountered in Runnable request", t); throwable = t; } ch.down( new ExecutorEvent( ExecutorEvent.TASK_COMPLETE, throwable != null ? new Object[] {runnable, throwable} : runnable)); // We have to let the outer thread know we can now be interrupted shutdownLock.lock(); try { canInterrupt.set(true); } finally { shutdownLock.unlock(); } } _runnables.remove(currentThread); } }; executionThread.setName(Thread.currentThread().getName() + "- Task Runner"); executionThread.start(); try { executionThread.join(); } catch (InterruptedException e) { shutdownLock.lock(); try { if (canInterrupt.get()) { executionThread.interrupt(); } shutdown.set(true); } finally { shutdownLock.unlock(); } if (_logger.isTraceEnabled()) { _logger.trace("Shutting down Execution Runner"); } } } /** * Returns a copy of the runners being used with the runner and what threads. If a thread is not * currently running a task it will return with a null value. This map is a copy and can be * modified if necessary without causing issues. * * @return map of all threads that are active with this runner. If the thread is currently running * a job the runnable value will be populated otherwise null would mean the thread is waiting */ public Map<Thread, Runnable> getCurrentRunningTasks() { Map<Thread, Runnable> map = new HashMap<>(); for (Entry<Thread, Holder<Runnable>> entry : _runnables.entrySet()) { map.put(entry.getKey(), entry.getValue().value); } return map; } private final Map<Thread, Holder<Runnable>> _runnables = new ConcurrentHashMap<>(); protected static final Log _logger = LogFactory.getLog(ExecutionRunner.class); }
/** * A DynamicMBean wrapping an annotated object instance. * * @author Chris Mills * @author Vladimir Blagojevic * @version $Id: ResourceDMBean.java,v 1.32 2009/12/11 13:21:17 belaban Exp $ * @see ManagedAttribute * @see ManagedOperation * @see MBean */ public class ResourceDMBean implements DynamicMBean { private static final Class<?>[] primitives = { int.class, byte.class, short.class, long.class, float.class, double.class, boolean.class, char.class }; private static final String MBEAN_DESCRITION = "Dynamic MBean Description"; private final Log log = LogFactory.getLog(ResourceDMBean.class); private final Object obj; private String description = ""; private final MBeanAttributeInfo[] attrInfo; private final MBeanOperationInfo[] opInfo; private final HashMap<String, AttributeEntry> atts = new HashMap<String, AttributeEntry>(); private final List<MBeanOperationInfo> ops = new ArrayList<MBeanOperationInfo>(); public ResourceDMBean(Object instance) { if (instance == null) throw new NullPointerException("Cannot make an MBean wrapper for null instance"); this.obj = instance; findDescription(); findFields(); findMethods(); attrInfo = new MBeanAttributeInfo[atts.size()]; int i = 0; MBeanAttributeInfo info = null; for (AttributeEntry entry : atts.values()) { info = entry.getInfo(); attrInfo[i++] = info; } opInfo = new MBeanOperationInfo[ops.size()]; ops.toArray(opInfo); } Object getObject() { return obj; } private void findDescription() { MBean mbean = getObject().getClass().getAnnotation(MBean.class); if (mbean != null && mbean.description() != null && mbean.description().trim().length() > 0) { description = mbean.description(); MBeanAttributeInfo info = new MBeanAttributeInfo( ResourceDMBean.MBEAN_DESCRITION, "java.lang.String", "MBean description", true, false, false); try { atts.put( ResourceDMBean.MBEAN_DESCRITION, new FieldAttributeEntry(info, getClass().getDeclaredField("description"))); } catch (NoSuchFieldException e) { // this should not happen unless somebody removes description field log.warn("Could not reflect field description of this class. Was it removed?"); } } } public synchronized MBeanInfo getMBeanInfo() { return new MBeanInfo( getObject().getClass().getCanonicalName(), description, attrInfo, null, opInfo, null); } public synchronized Object getAttribute(String name) { if (name == null || name.length() == 0) throw new NullPointerException("Invalid attribute requested " + name); Attribute attr = getNamedAttribute(name); return attr.getValue(); } public synchronized void setAttribute(Attribute attribute) { if (attribute == null || attribute.getName() == null) throw new NullPointerException("Invalid attribute requested " + attribute); setNamedAttribute(attribute); } public synchronized AttributeList getAttributes(String[] names) { AttributeList al = new AttributeList(); for (String name : names) { Attribute attr = getNamedAttribute(name); if (attr != null) { al.add(attr); } else { log.warn("Did not find attribute " + name); } } return al; } public synchronized AttributeList setAttributes(AttributeList list) { AttributeList results = new AttributeList(); for (int i = 0; i < list.size(); i++) { Attribute attr = (Attribute) list.get(i); if (setNamedAttribute(attr)) { results.add(attr); } else { if (log.isWarnEnabled()) { log.warn( "Failed to update attribute name " + attr.getName() + " with value " + attr.getValue()); } } } return results; } public Object invoke(String name, Object[] args, String[] sig) throws MBeanException, ReflectionException { try { Class<?>[] classes = new Class[sig.length]; for (int i = 0; i < classes.length; i++) { classes[i] = getClassForName(sig[i]); } Method method = getObject().getClass().getMethod(name, classes); return method.invoke(getObject(), args); } catch (Exception e) { throw new MBeanException(e); } } public static Class<?> getClassForName(String name) throws ClassNotFoundException { try { return Class.forName(name); } catch (ClassNotFoundException cnfe) { // Could be a primitive - let's check for (int i = 0; i < primitives.length; i++) { if (name.equals(primitives[i].getName())) { return primitives[i]; } } } throw new ClassNotFoundException("Class " + name + " cannot be found"); } private void findMethods() { // find all methods but don't include methods from Object class List<Method> methods = new ArrayList<Method>(Arrays.asList(getObject().getClass().getMethods())); List<Method> objectMethods = new ArrayList<Method>(Arrays.asList(Object.class.getMethods())); methods.removeAll(objectMethods); for (Method method : methods) { // does method have @ManagedAttribute annotation? if (method.isAnnotationPresent(ManagedAttribute.class) || method.isAnnotationPresent(Property.class)) { exposeManagedAttribute(method); } // or @ManagedOperation else if (method.isAnnotationPresent(ManagedOperation.class) || isMBeanAnnotationPresentWithExposeAll()) { exposeManagedOperation(method); } } } private void exposeManagedOperation(Method method) { ManagedOperation op = method.getAnnotation(ManagedOperation.class); String attName = method.getName(); if (isSetMethod(method) || isGetMethod(method)) { attName = attName.substring(3); } else if (isIsMethod(method)) { attName = attName.substring(2); } // expose unless we already exposed matching attribute field boolean isAlreadyExposed = atts.containsKey(attName); if (!isAlreadyExposed) { ops.add(new MBeanOperationInfo(op != null ? op.description() : "", method)); } } private void exposeManagedAttribute(Method method) { String methodName = method.getName(); if (!methodName.startsWith("get") && !methodName.startsWith("set") && !methodName.startsWith("is")) { if (log.isWarnEnabled()) log.warn( "method name " + methodName + " doesn't start with \"get\", \"set\", or \"is\"" + ", but is annotated with @ManagedAttribute: will be ignored"); return; } ManagedAttribute attr = method.getAnnotation(ManagedAttribute.class); Property prop = method.getAnnotation(Property.class); boolean expose_prop = prop != null && prop.exposeAsManagedAttribute(); boolean expose = attr != null || expose_prop; if (!expose) return; // Is name field of @ManagedAttributed used? String attributeName = attr != null ? attr.name() : null; if (attributeName != null && attributeName.trim().length() > 0) attributeName = attributeName.trim(); else attributeName = null; String descr = attr != null ? attr.description() : prop != null ? prop.description() : null; boolean writeAttribute = false; MBeanAttributeInfo info = null; if (isSetMethod(method)) { // setter attributeName = (attributeName == null) ? methodName.substring(3) : attributeName; info = new MBeanAttributeInfo( attributeName, method.getParameterTypes()[0].getCanonicalName(), descr, true, true, false); writeAttribute = true; } else { // getter if (method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE) { boolean hasSetter = atts.containsKey(attributeName); // we found is method if (methodName.startsWith("is")) { attributeName = (attributeName == null) ? methodName.substring(2) : attributeName; info = new MBeanAttributeInfo( attributeName, method.getReturnType().getCanonicalName(), descr, true, hasSetter, true); } else { // this has to be get attributeName = (attributeName == null) ? methodName.substring(3) : attributeName; info = new MBeanAttributeInfo( attributeName, method.getReturnType().getCanonicalName(), descr, true, hasSetter, false); } } else { if (log.isWarnEnabled()) { log.warn( "Method " + method.getName() + " must have a valid return type and zero parameters"); } // silently skip this method return; } } AttributeEntry ae = atts.get(attributeName); // is it a read method? if (!writeAttribute) { // we already have annotated field as read if (ae instanceof FieldAttributeEntry && ae.getInfo().isReadable()) { log.warn("not adding annotated method " + method + " since we already have read attribute"); } // we already have annotated set method else if (ae instanceof MethodAttributeEntry) { MethodAttributeEntry mae = (MethodAttributeEntry) ae; if (mae.hasSetMethod()) { atts.put( attributeName, new MethodAttributeEntry(mae.getInfo(), mae.getSetMethod(), method)); } } // we don't have such entry else { atts.put( attributeName, new MethodAttributeEntry(info, findSetter(obj.getClass(), attributeName), method)); } } // is it a set method? else { if (ae instanceof FieldAttributeEntry) { // we already have annotated field as write if (ae.getInfo().isWritable()) { log.warn( "Not adding annotated method " + methodName + " since we already have writable attribute"); } else { // we already have annotated field as read // lets make the field writable Field f = ((FieldAttributeEntry) ae).getField(); MBeanAttributeInfo i = new MBeanAttributeInfo( ae.getInfo().getName(), f.getType().getCanonicalName(), descr, true, !Modifier.isFinal(f.getModifiers()), false); atts.put(attributeName, new FieldAttributeEntry(i, f)); } } // we already have annotated getOrIs method else if (ae instanceof MethodAttributeEntry) { MethodAttributeEntry mae = (MethodAttributeEntry) ae; if (mae.hasIsOrGetMethod()) { atts.put(attributeName, new MethodAttributeEntry(info, method, mae.getIsOrGetMethod())); } } // we don't have such entry else { atts.put( attributeName, new MethodAttributeEntry(info, method, findGetter(obj.getClass(), attributeName))); } } } private static boolean isSetMethod(Method method) { return (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && method.getReturnType() == java.lang.Void.TYPE); } private static boolean isGetMethod(Method method) { return (method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE && method.getName().startsWith("get")); } private static boolean isIsMethod(Method method) { return (method.getParameterTypes().length == 0 && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class) && method.getName().startsWith("is")); } public static Method findGetter(Class clazz, String name) { try { return clazz.getMethod("get" + name); } catch (NoSuchMethodException e) { } try { return clazz.getMethod("is" + name); } catch (NoSuchMethodException ex) { } return null; } public static Method findSetter(Class clazz, String name) { try { return clazz.getMethod("set" + name); } catch (NoSuchMethodException e) { } return null; } private void findFields() { // traverse class hierarchy and find all annotated fields for (Class<?> clazz = getObject().getClass(); clazz != null; clazz = clazz.getSuperclass()) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { ManagedAttribute attr = field.getAnnotation(ManagedAttribute.class); Property prop = field.getAnnotation(Property.class); boolean expose_prop = prop != null && prop.exposeAsManagedAttribute(); boolean expose = attr != null || expose_prop; if (expose) { String fieldName = Util.attributeNameToMethodName(field.getName()); String descr = attr != null ? attr.description() : prop.description(); boolean writable = attr != null ? attr.writable() : prop.writable(); MBeanAttributeInfo info = new MBeanAttributeInfo( fieldName, field.getType().getCanonicalName(), descr, true, !Modifier.isFinal(field.getModifiers()) && writable, false); atts.put(fieldName, new FieldAttributeEntry(info, field)); } } } } private Attribute getNamedAttribute(String name) { Attribute result = null; if (name.equals(ResourceDMBean.MBEAN_DESCRITION)) { result = new Attribute(ResourceDMBean.MBEAN_DESCRITION, this.description); } else { AttributeEntry entry = atts.get(name); if (entry != null) { try { result = new Attribute(name, entry.invoke(null)); } catch (Exception e) { log.warn("Exception while reading value of attribute " + name, e); } } else { log.warn("Did not find queried attribute with name " + name); } } return result; } private boolean setNamedAttribute(Attribute attribute) { boolean result = false; AttributeEntry entry = atts.get(attribute.getName()); if (entry != null) { try { entry.invoke(attribute); result = true; } catch (Exception e) { log.warn("Exception while writing value for attribute " + attribute.getName(), e); } } else { log.warn( "Could not invoke set on attribute " + attribute.getName() + " with value " + attribute.getValue()); } return result; } private boolean isMBeanAnnotationPresentWithExposeAll() { Class<? extends Object> c = getObject().getClass(); return c.isAnnotationPresent(MBean.class) && c.getAnnotation(MBean.class).exposeAll(); } private class MethodAttributeEntry implements AttributeEntry { final MBeanAttributeInfo info; final Method isOrGetmethod; final Method setMethod; public MethodAttributeEntry( final MBeanAttributeInfo info, final Method setMethod, final Method isOrGetMethod) { super(); this.info = info; this.setMethod = setMethod; this.isOrGetmethod = isOrGetMethod; } public Object invoke(Attribute a) throws Exception { if (a == null && isOrGetmethod != null) return isOrGetmethod.invoke(getObject()); else if (a != null && setMethod != null) return setMethod.invoke(getObject(), a.getValue()); else return null; } public MBeanAttributeInfo getInfo() { return info; } public boolean hasIsOrGetMethod() { return isOrGetmethod != null; } public boolean hasSetMethod() { return setMethod != null; } public Method getIsOrGetMethod() { return isOrGetmethod; } public Method getSetMethod() { return setMethod; } } private class FieldAttributeEntry implements AttributeEntry { private final MBeanAttributeInfo info; private final Field field; public FieldAttributeEntry(final MBeanAttributeInfo info, final Field field) { super(); this.info = info; this.field = field; if (!field.isAccessible()) { field.setAccessible(true); } } public Field getField() { return field; } public Object invoke(Attribute a) throws Exception { if (a == null) { return field.get(getObject()); } else { field.set(getObject(), a.getValue()); return null; } } public MBeanAttributeInfo getInfo() { return info; } } private interface AttributeEntry { public Object invoke(Attribute a) throws Exception; public MBeanAttributeInfo getInfo(); } }
/** * Provides synchronous and asynchronous message sending with request-response correlation; i.e., * matching responses with the original request. It also offers push-style message reception (by * internally using the PullPushAdapter). * * <p>Channels are simple patterns to asynchronously send a receive messages. However, a significant * number of communication patterns in group communication require synchronous communication. For * example, a sender would like to send a message to the group and wait for all responses. Or * another application would like to send a message to the group and wait only until the majority of * the receivers have sent a response, or until a timeout occurred. MessageDispatcher offers a * combination of the above pattern with other patterns. * * <p>Used on top of channel to implement group requests. Client's <code>handle()</code> method is * called when request is received. Is the equivalent of RpcProtocol on the application instead of * protocol level. * * @author Bela Ban */ public class MessageDispatcher implements AsyncRequestHandler, ChannelListener { protected Channel channel; protected RequestCorrelator corr; protected MessageListener msg_listener; protected MembershipListener membership_listener; protected RequestHandler req_handler; protected boolean async_dispatching; protected ProtocolAdapter prot_adapter; protected volatile Collection<Address> members = new HashSet<Address>(); protected Address local_addr; protected final Log log = LogFactory.getLog(getClass()); protected boolean hardware_multicast_supported = false; protected final AtomicInteger sync_unicasts = new AtomicInteger(0); protected final AtomicInteger async_unicasts = new AtomicInteger(0); protected final AtomicInteger sync_multicasts = new AtomicInteger(0); protected final AtomicInteger async_multicasts = new AtomicInteger(0); protected final AtomicInteger sync_anycasts = new AtomicInteger(0); protected final AtomicInteger async_anycasts = new AtomicInteger(0); protected final Set<ChannelListener> channel_listeners = new CopyOnWriteArraySet<ChannelListener>(); protected final DiagnosticsHandler.ProbeHandler probe_handler = new MyProbeHandler(); public MessageDispatcher() {} public MessageDispatcher(Channel channel, MessageListener l, MembershipListener l2) { this.channel = channel; prot_adapter = new ProtocolAdapter(); if (channel != null) { local_addr = channel.getAddress(); channel.addChannelListener(this); } setMessageListener(l); setMembershipListener(l2); if (channel != null) installUpHandler(prot_adapter, true); start(); } public MessageDispatcher(Channel channel, RequestHandler req_handler) { this(channel, null, null, req_handler); } public MessageDispatcher( Channel channel, MessageListener l, MembershipListener l2, RequestHandler req_handler) { this(channel, l, l2); setRequestHandler(req_handler); } public boolean asyncDispatching() { return async_dispatching; } public MessageDispatcher asyncDispatching(boolean flag) { async_dispatching = flag; if (corr != null) corr.asyncDispatching(flag); return this; } public UpHandler getProtocolAdapter() { return prot_adapter; } /** * If this dispatcher is using a user-provided PullPushAdapter, then need to set the members from * the adapter initially since viewChange has most likely already been called in PullPushAdapter. */ protected void setMembers(List<Address> new_mbrs) { if (new_mbrs != null) members = new HashSet<Address>(new_mbrs); // volatile write - seen by a subsequent read } /** Adds a new channel listener to be notified on the channel's state change. */ public void addChannelListener(ChannelListener l) { if (l != null) channel_listeners.add(l); } public void removeChannelListener(ChannelListener l) { if (l != null) channel_listeners.remove(l); } public void start() { if (corr == null) corr = createRequestCorrelator(prot_adapter, this, local_addr) .asyncDispatching(async_dispatching); correlatorStarted(); corr.start(); if (channel != null) { List<Address> tmp_mbrs = channel.getView() != null ? channel.getView().getMembers() : null; setMembers(tmp_mbrs); if (channel instanceof JChannel) { TP transport = channel.getProtocolStack().getTransport(); corr.registerProbeHandler(transport); } TP transport = channel.getProtocolStack().getTransport(); hardware_multicast_supported = transport.supportsMulticasting(); transport.registerProbeHandler(probe_handler); } } protected RequestCorrelator createRequestCorrelator( Protocol transport, RequestHandler handler, Address local_addr) { return new RequestCorrelator(transport, handler, local_addr); } protected void correlatorStarted() { ; } public void stop() { if (corr != null) corr.stop(); if (channel instanceof JChannel) { TP transport = channel.getProtocolStack().getTransport(); transport.unregisterProbeHandler(probe_handler); corr.unregisterProbeHandler(transport); } } public final void setMessageListener(MessageListener l) { msg_listener = l; } public MessageListener getMessageListener() { return msg_listener; } public final void setMembershipListener(MembershipListener l) { membership_listener = l; } public final void setRequestHandler(RequestHandler rh) { req_handler = rh; } public Channel getChannel() { return channel; } public void setChannel(Channel ch) { if (ch == null) return; this.channel = ch; local_addr = channel.getAddress(); if (prot_adapter == null) prot_adapter = new ProtocolAdapter(); // Don't force installing the UpHandler so subclasses can use this // method and still integrate with a MuxUpHandler installUpHandler(prot_adapter, false); } /** * Sets the given UpHandler as the UpHandler for the channel, or, if the channel already has a * Muxer installed as it's UpHandler, sets the given handler as the Muxer's {@link * Muxer#setDefaultHandler(Object) default handler}. If the relevant handler is already installed, * the <code>canReplace</code> controls whether this method replaces it (after logging a WARN) or * simply leaves <code>handler</code> uninstalled. * * <p>Passing <code>false</code> as the <code>canReplace</code> value allows callers to use this * method to install defaults without concern about inadvertently overriding * * @param handler the UpHandler to install * @param canReplace <code>true</code> if an existing Channel upHandler or Muxer default upHandler * can be replaced; <code>false</code> if this method shouldn't install */ protected void installUpHandler(UpHandler handler, boolean canReplace) { UpHandler existing = channel.getUpHandler(); if (existing == null) { channel.setUpHandler(handler); } else if (existing instanceof Muxer<?>) { @SuppressWarnings("unchecked") Muxer<UpHandler> mux = (Muxer<UpHandler>) existing; if (mux.getDefaultHandler() == null) { mux.setDefaultHandler(handler); } else if (canReplace) { log.warn( "Channel Muxer already has a default up handler installed (" + mux.getDefaultHandler() + ") but now it is being overridden"); mux.setDefaultHandler(handler); } } else if (canReplace) { log.warn( "Channel already has an up handler installed (" + existing + ") but now it is being overridden"); channel.setUpHandler(handler); } } /** * Sends a message to all members and expects responses from members in dests (if non-null). * * @param dests A list of group members from which to expect responses (if the call is blocking). * @param msg The message to be sent * @param options A set of options that govern the call. See {@link * org.jgroups.blocks.RequestOptions} for details * @return RspList A list of Rsp elements * @throws Exception If the request cannot be sent * @since 2.9 */ public <T> RspList<T> castMessage( final Collection<Address> dests, Message msg, RequestOptions options) throws Exception { GroupRequest<T> req = cast(dests, msg, options, true); return req != null ? req.getResults() : new RspList(); } /** * Sends a message to all members and expects responses from members in dests (if non-null). * * @param dests A list of group members from which to expect responses (if the call is blocking). * @param msg The message to be sent * @param options A set of options that govern the call. See {@link * org.jgroups.blocks.RequestOptions} for details * @param listener A FutureListener which will be registered (if non null) with the future * <em>before</em> the call is invoked * @return NotifyingFuture<T> A future from which the results (RspList) can be retrieved * @throws Exception If the request cannot be sent */ public <T> NotifyingFuture<RspList<T>> castMessageWithFuture( final Collection<Address> dests, Message msg, RequestOptions options, FutureListener<RspList<T>> listener) throws Exception { GroupRequest<T> req = cast(dests, msg, options, false, listener); return req != null ? req : new NullFuture<RspList>(new RspList()); } /** * Sends a message to all members and expects responses from members in dests (if non-null). * * @param dests A list of group members from which to expect responses (if the call is blocking). * @param msg The message to be sent * @param options A set of options that govern the call. See {@link * org.jgroups.blocks.RequestOptions} for details * @return NotifyingFuture<T> A future from which the results (RspList) can be retrieved * @throws Exception If the request cannot be sent */ public <T> NotifyingFuture<RspList<T>> castMessageWithFuture( final Collection<Address> dests, Message msg, RequestOptions options) throws Exception { return castMessageWithFuture(dests, msg, options, null); } protected <T> GroupRequest<T> cast( final Collection<Address> dests, Message msg, RequestOptions options, boolean block_for_results, FutureListener<RspList<T>> listener) throws Exception { if (msg.getDest() != null && !(msg.getDest() instanceof AnycastAddress)) throw new IllegalArgumentException("message destination is non-null, cannot send message"); if (options != null) { msg.setFlag(options.getFlags()).setTransientFlag(options.getTransientFlags()); if (options.getScope() > 0) msg.setScope(options.getScope()); } List<Address> real_dests; // we need to clone because we don't want to modify the original if (dests != null) { real_dests = new ArrayList<Address>(); for (Address dest : dests) { if (dest instanceof SiteAddress || this.members.contains(dest)) { if (!real_dests.contains(dest)) real_dests.add(dest); } } } else real_dests = new ArrayList<Address>(members); // if local delivery is off, then we should not wait for the message from the local member. // therefore remove it from the membership Channel tmp = channel; if ((tmp != null && tmp.getDiscardOwnMessages()) || msg.isTransientFlagSet(Message.TransientFlag.DONT_LOOPBACK)) { if (local_addr == null) local_addr = tmp != null ? tmp.getAddress() : null; if (local_addr != null) real_dests.remove(local_addr); } if (options != null && options.hasExclusionList()) { Address[] exclusion_list = options.exclusionList(); for (Address excluding : exclusion_list) real_dests.remove(excluding); } // don't even send the message if the destination list is empty if (log.isTraceEnabled()) log.trace("real_dests=" + real_dests); if (real_dests.isEmpty()) { if (log.isTraceEnabled()) log.trace("destination list is empty, won't send message"); return null; } if (options != null) { boolean async = options.getMode() == ResponseMode.GET_NONE; if (options.getAnycasting()) { if (async) async_anycasts.incrementAndGet(); else sync_anycasts.incrementAndGet(); } else { if (async) async_multicasts.incrementAndGet(); else sync_multicasts.incrementAndGet(); } } GroupRequest<T> req = new GroupRequest<T>(msg, corr, real_dests, options); if (listener != null) req.setListener(listener); if (options != null) { req.setResponseFilter(options.getRspFilter()); req.setAnycasting(options.getAnycasting()); } req.setBlockForResults(block_for_results); req.execute(); return req; } protected <T> GroupRequest<T> cast( final Collection<Address> dests, Message msg, RequestOptions options, boolean block_for_results) throws Exception { return cast(dests, msg, options, block_for_results, null); } public void done(long req_id) { corr.done(req_id); } /** * Sends a unicast message and - depending on the options - returns a result * * @param msg the message to be sent. The destination needs to be non-null * @param opts the options to be used * @return T the result * @throws Exception If there was problem sending the request, processing it at the receiver, or * processing it at the sender. * @throws TimeoutException If the call didn't succeed within the timeout defined in options (if * set) */ public <T> T sendMessage(Message msg, RequestOptions opts) throws Exception { Address dest = msg.getDest(); if (dest == null) throw new IllegalArgumentException("message destination is null, cannot send message"); if (opts != null) { msg.setFlag(opts.getFlags()).setTransientFlag(opts.getTransientFlags()); if (opts.getScope() > 0) msg.setScope(opts.getScope()); if (opts.getMode() == ResponseMode.GET_NONE) async_unicasts.incrementAndGet(); else sync_unicasts.incrementAndGet(); } UnicastRequest<T> req = new UnicastRequest<T>(msg, corr, dest, opts); req.execute(); if (opts != null && opts.getMode() == ResponseMode.GET_NONE) return null; Rsp<T> rsp = req.getResult(); if (rsp.wasSuspected()) throw new SuspectedException(dest); Throwable exception = rsp.getException(); if (exception != null) { if (exception instanceof Error) throw (Error) exception; else if (exception instanceof RuntimeException) throw (RuntimeException) exception; else if (exception instanceof Exception) throw (Exception) exception; else throw new RuntimeException(exception); } if (rsp.wasUnreachable()) throw new UnreachableException(dest); if (!rsp.wasReceived() && !req.responseReceived()) throw new TimeoutException("timeout sending message to " + dest); return rsp.getValue(); } /** * Sends a unicast message to the target defined by msg.getDest() and returns a future * * @param msg The unicast message to be sent. msg.getDest() must not be null * @param options * @param listener A FutureListener which will be registered (if non null) with the future * <em>before</em> the call is invoked * @return NotifyingFuture<T> A future from which the result can be fetched * @throws Exception If there was problem sending the request, processing it at the receiver, or * processing it at the sender. {@link java.util.concurrent.Future#get()} will throw this * exception * @throws TimeoutException If the call didn't succeed within the timeout defined in options (if * set) */ public <T> NotifyingFuture<T> sendMessageWithFuture( Message msg, RequestOptions options, FutureListener<T> listener) throws Exception { Address dest = msg.getDest(); if (dest == null) throw new IllegalArgumentException("message destination is null, cannot send message"); if (options != null) { msg.setFlag(options.getFlags()).setTransientFlag(options.getTransientFlags()); if (options.getScope() > 0) msg.setScope(options.getScope()); if (options.getMode() == ResponseMode.GET_NONE) async_unicasts.incrementAndGet(); else sync_unicasts.incrementAndGet(); } UnicastRequest<T> req = new UnicastRequest<T>(msg, corr, dest, options); if (listener != null) req.setListener(listener); req.setBlockForResults(false); req.execute(); if (options != null && options.getMode() == ResponseMode.GET_NONE) return new NullFuture<T>(null); return req; } /** * Sends a unicast message to the target defined by msg.getDest() and returns a future * * @param msg The unicast message to be sent. msg.getDest() must not be null * @param options * @return NotifyingFuture<T> A future from which the result can be fetched * @throws Exception If there was problem sending the request, processing it at the receiver, or * processing it at the sender. {@link java.util.concurrent.Future#get()} will throw this * exception * @throws TimeoutException If the call didn't succeed within the timeout defined in options (if * set) */ public <T> NotifyingFuture<T> sendMessageWithFuture(Message msg, RequestOptions options) throws Exception { return sendMessageWithFuture(msg, options, null); } /* ------------------------ RequestHandler Interface ---------------------- */ public Object handle(Message msg) throws Exception { if (req_handler != null) return req_handler.handle(msg); return null; } /* -------------------- End of RequestHandler Interface ------------------- */ /* -------------------- AsyncRequestHandler Interface --------------------- */ public void handle(Message request, Response response) throws Exception { if (req_handler != null) { if (req_handler instanceof AsyncRequestHandler) ((AsyncRequestHandler) req_handler).handle(request, response); else { Object retval = req_handler.handle(request); if (response != null) response.send(retval, false); } return; } Object retval = handle(request); if (response != null) response.send(retval, false); } /* ------------------ End of AsyncRequestHandler Interface----------------- */ /* --------------------- Interface ChannelListener ---------------------- */ public void channelConnected(Channel channel) { for (ChannelListener l : channel_listeners) { try { l.channelConnected(channel); } catch (Throwable t) { log.warn("notifying channel listener " + l + " failed", t); } } } public void channelDisconnected(Channel channel) { stop(); for (ChannelListener l : channel_listeners) { try { l.channelDisconnected(channel); } catch (Throwable t) { log.warn("notifying channel listener " + l + " failed", t); } } } public void channelClosed(Channel channel) { stop(); for (ChannelListener l : channel_listeners) { try { l.channelClosed(channel); } catch (Throwable t) { log.warn("notifying channel listener " + l + " failed", t); } } } /* ----------------------------------------------------------------------- */ protected Object handleUpEvent(Event evt) throws Exception { switch (evt.getType()) { case Event.MSG: if (msg_listener != null) msg_listener.receive((Message) evt.getArg()); break; case Event.GET_APPLSTATE: // reply with GET_APPLSTATE_OK byte[] tmp_state = null; if (msg_listener != null) { ByteArrayOutputStream output = new ByteArrayOutputStream(1024); msg_listener.getState(output); tmp_state = output.toByteArray(); } return new StateTransferInfo(null, 0L, tmp_state); case Event.GET_STATE_OK: if (msg_listener != null) { StateTransferResult result = (StateTransferResult) evt.getArg(); if (result.hasBuffer()) { ByteArrayInputStream input = new ByteArrayInputStream(result.getBuffer()); msg_listener.setState(input); } } break; case Event.STATE_TRANSFER_OUTPUTSTREAM: OutputStream os = (OutputStream) evt.getArg(); if (msg_listener != null && os != null) { msg_listener.getState(os); } break; case Event.STATE_TRANSFER_INPUTSTREAM: InputStream is = (InputStream) evt.getArg(); if (msg_listener != null && is != null) msg_listener.setState(is); break; case Event.VIEW_CHANGE: View v = (View) evt.getArg(); List<Address> new_mbrs = v.getMembers(); setMembers(new_mbrs); if (membership_listener != null) membership_listener.viewAccepted(v); break; case Event.SET_LOCAL_ADDRESS: if (log.isTraceEnabled()) log.trace("setting local_addr (" + local_addr + ") to " + evt.getArg()); local_addr = (Address) evt.getArg(); break; case Event.SUSPECT: if (membership_listener != null) membership_listener.suspect((Address) evt.getArg()); break; case Event.BLOCK: if (membership_listener != null) membership_listener.block(); break; case Event.UNBLOCK: if (membership_listener != null) membership_listener.unblock(); break; } return null; } class MyProbeHandler implements DiagnosticsHandler.ProbeHandler { public Map<String, String> handleProbe(String... keys) { Map<String, String> retval = new HashMap<String, String>(); for (String key : keys) { if ("rpcs".equals(key)) { String channel_name = channel != null ? channel.getClusterName() : ""; retval.put(channel_name + ": sync unicast RPCs", sync_unicasts.toString()); retval.put(channel_name + ": sync multicast RPCs", sync_multicasts.toString()); retval.put(channel_name + ": async unicast RPCs", async_unicasts.toString()); retval.put(channel_name + ": async multicast RPCs", async_multicasts.toString()); retval.put(channel_name + ": sync anycast RPCs", sync_anycasts.toString()); retval.put(channel_name + ": async anycast RPCs", async_anycasts.toString()); } if ("rpcs-reset".equals(key)) { sync_unicasts.set(0); sync_multicasts.set(0); async_unicasts.set(0); async_multicasts.set(0); sync_anycasts.set(0); async_anycasts.set(0); } } return retval; } public String[] supportedKeys() { return new String[] {"rpcs", "rpcs-reset"}; } } class ProtocolAdapter extends Protocol implements UpHandler { /* ------------------------- Protocol Interface --------------------------- */ public String getName() { return "MessageDispatcher"; } /** * Called by channel (we registered before) when event is received. This is the UpHandler * interface. */ public Object up(Event evt) { if (corr != null) { if (!corr.receive(evt)) { try { return handleUpEvent(evt); } catch (Throwable t) { throw new RuntimeException(t); } } } return null; } public Object down(Event evt) { if (channel != null) { if (evt.getType() == Event.MSG && !(channel.isConnected() || channel.isConnecting())) throw new IllegalStateException("channel is not connected"); return channel.down(evt); } return null; } /* ----------------------- End of Protocol Interface ------------------------ */ } }
/** * The Protocol class provides a set of common services for protocol layers. Each layer has to be a * subclass of Protocol and override a number of methods (typically just <code>up()</code>, <code> * down()</code> and <code>getName()</code>. Layers are stacked in a certain order to form a * protocol stack. <a href=org.jgroups.Event.html>Events</a> are passed from lower layers to upper * ones and vice versa. E.g. a Message received by the UDP layer at the bottom will be passed to its * higher layer as an Event. That layer will in turn pass the Event to its layer and so on, until a * layer handles the Message and sends a response or discards it, the former resulting in another * Event being passed down the stack. * * <p>The important thing to bear in mind is that Events have to passed on between layers in FIFO * order which is guaranteed by the Protocol implementation and must be guranteed by subclasses * implementing their on Event queuing. * * <p><b>Note that each class implementing interface Protocol MUST provide an empty, public * constructor !</b> * * @author Bela Ban */ public abstract class Protocol { protected Protocol up_prot = null, down_prot = null; protected ProtocolStack stack = null; @Property( description = "Determines whether to collect statistics (and expose them via JMX). Default is true", writable = true) protected boolean stats = true; @Property( description = "Enables ergonomics: dynamically find the best values for properties at runtime") protected boolean ergonomics = true; /** * The name of the protocol. Is by default set to the protocol's classname. This property should * rarely need to be set, e.g. only in cases where we want to create more than 1 protocol of the * same class in the same stack */ @Property( name = "name", description = "Give the protocol a different name if needed so we can have multiple " + "instances of it in the same stack (also change ID)", writable = false) protected String name = getClass().getSimpleName(); @Property( description = "Give the protocol a different ID if needed so we can have multiple " + "instances of it in the same stack", writable = false) protected short id = ClassConfigurator.getProtocolId(getClass()); protected final Log log = LogFactory.getLog(this.getClass()); /** * Sets the level of a logger. This method is used to dynamically change the logging level of a * running system, e.g. via JMX. The appender of a level needs to exist. * * @param level The new level. Valid values are "fatal", "error", "warn", "info", "debug", "trace" * (capitalization not relevant) */ @Property(name = "level", description = "Sets the logger level (see javadocs)") public void setLevel(String level) { log.setLevel(level); } public String getLevel() { return log.getLevel(); } public boolean isErgonomics() { return ergonomics; } public void setErgonomics(boolean ergonomics) { this.ergonomics = ergonomics; } public Object getValue(String name) { if (name == null) return null; Field field = Util.getField(getClass(), name); if (field == null) throw new IllegalArgumentException("field \"" + name + "\n not found"); return Util.getField(field, this); } public Protocol setValues(Map<String, Object> values) { if (values == null) return this; for (Map.Entry<String, Object> entry : values.entrySet()) { String attrname = entry.getKey(); Object value = entry.getValue(); Field field = Util.getField(getClass(), attrname); if (field != null) { Util.setField(field, this, value); } } return this; } public Protocol setValue(String name, Object value) { if (name == null || value == null) return this; Field field = Util.getField(getClass(), name); if (field == null) throw new IllegalArgumentException("field \"" + name + "\n not found"); Util.setField(field, this, value); return this; } public ProtocolStack getProtocolStack() { return stack; } /** * After configuring the protocol itself from the properties defined in the XML config, a protocol * might have additional objects which need to be configured. This callback allows a protocol * developer to configure those other objects. This call is guaranteed to be invoked * <em>after</em> the protocol itself has been configured. See AUTH for an example. * * @return */ protected List<Object> getConfigurableObjects() { return null; } protected TP getTransport() { Protocol retval = this; while (retval != null && retval.down_prot != null) { retval = retval.down_prot; } return (TP) retval; } /** * Supposed to be overwritten by subclasses. Usually the transport returns a valid non-null thread * factory, but thread factories can also be created by individual protocols * * @return */ public ThreadFactory getThreadFactory() { return down_prot != null ? down_prot.getThreadFactory() : null; } /** * Returns the SocketFactory associated with this protocol, if overridden in a subclass, or passes * the call down * * @return SocketFactory */ public SocketFactory getSocketFactory() { return down_prot != null ? down_prot.getSocketFactory() : null; } /** * Sets a SocketFactory. Socket factories are typically provided by the transport ({@link * org.jgroups.protocols.TP}) or {@link org.jgroups.protocols.TP.ProtocolAdapter} * * @param factory */ public void setSocketFactory(SocketFactory factory) { if (down_prot != null) down_prot.setSocketFactory(factory); } public boolean statsEnabled() { return stats; } public void enableStats(boolean flag) { stats = flag; } public void resetStats() { ; } public String printStats() { return null; } public Map<String, Object> dumpStats() { HashMap<String, Object> map = new HashMap<String, Object>(); for (Class<?> clazz = this.getClass(); clazz != null; clazz = clazz.getSuperclass()) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(ManagedAttribute.class) || (field.isAnnotationPresent(Property.class) && field.getAnnotation(Property.class).exposeAsManagedAttribute())) { String attributeName = field.getName(); try { field.setAccessible(true); Object value = field.get(this); map.put(attributeName, value != null ? value.toString() : null); } catch (Exception e) { log.warn("Could not retrieve value of attribute (field) " + attributeName, e); } } } Method[] methods = this.getClass().getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(ManagedAttribute.class) || (method.isAnnotationPresent(Property.class) && method.getAnnotation(Property.class).exposeAsManagedAttribute())) { String method_name = method.getName(); if (method_name.startsWith("is") || method_name.startsWith("get")) { try { Object value = method.invoke(this); String attributeName = Util.methodNameToAttributeName(method_name); map.put(attributeName, value != null ? value.toString() : null); } catch (Exception e) { log.warn("Could not retrieve value of attribute (method) " + method_name, e); } } else if (method_name.startsWith("set")) { String stem = method_name.substring(3); Method getter = ResourceDMBean.findGetter(getClass(), stem); if (getter != null) { try { Object value = getter.invoke(this); String attributeName = Util.methodNameToAttributeName(method_name); map.put(attributeName, value != null ? value.toString() : null); } catch (Exception e) { log.warn("Could not retrieve value of attribute (method) " + method_name, e); } } } } } } return map; } /** * Called after instance has been created (null constructor) and before protocol is started. * Properties are already set. Other protocols are not yet connected and events cannot yet be * sent. * * @exception Exception Thrown if protocol cannot be initialized successfully. This will cause the * ProtocolStack to fail, so the channel constructor will throw an exception */ public void init() throws Exception {} /** * This method is called on a {@link org.jgroups.Channel#connect(String)}. Starts work. Protocols * are connected and queues are ready to receive events. Will be called <em>from bottom to * top</em>. This call will replace the <b>START</b> and <b>START_OK</b> events. * * @exception Exception Thrown if protocol cannot be started successfully. This will cause the * ProtocolStack to fail, so {@link org.jgroups.Channel#connect(String)} will throw an * exception */ public void start() throws Exception {} /** * This method is called on a {@link org.jgroups.Channel#disconnect()}. Stops work (e.g. by * closing multicast socket). Will be called <em>from top to bottom</em>. This means that at the * time of the method invocation the neighbor protocol below is still working. This method will * replace the <b>STOP</b>, <b>STOP_OK</b>, <b>CLEANUP</b> and <b>CLEANUP_OK</b> events. The * ProtocolStack guarantees that when this method is called all messages in the down queue will * have been flushed */ public void stop() {} /** * This method is called on a {@link org.jgroups.Channel#close()}. Does some cleanup; after the * call the VM will terminate */ public void destroy() {} /** * List of events that are required to be answered by some layer above. * * @return Vector (of Integers) */ public List<Integer> requiredUpServices() { return null; } /** * List of events that are required to be answered by some layer below. * * @return Vector (of Integers) */ public List<Integer> requiredDownServices() { return null; } /** * List of events that are provided to layers above (they will be handled when sent down from * above). * * @return Vector (of Integers) */ public List<Integer> providedUpServices() { return null; } /** * List of events that are provided to layers below (they will be handled when sent down from * below). * * @return Vector<Integer (of Integers) */ public List<Integer> providedDownServices() { return null; } /** All protocol names have to be unique ! */ public String getName() { return name; } public short getId() { return id; } public void setId(short id) { this.id = id; } public Protocol getUpProtocol() { return up_prot; } public Protocol getDownProtocol() { return down_prot; } public void setUpProtocol(Protocol up_prot) { this.up_prot = up_prot; } public void setDownProtocol(Protocol down_prot) { this.down_prot = down_prot; } public void setProtocolStack(ProtocolStack stack) { this.stack = stack; } /** * An event was received from the layer below. Usually the current layer will want to examine the * event type and - depending on its type - perform some computation (e.g. removing headers from a * MSG event type, or updating the internal membership list when receiving a VIEW_CHANGE event). * Finally the event is either a) discarded, or b) an event is sent down the stack using <code> * down_prot.down()</code> or c) the event (or another event) is sent up the stack using <code> * up_prot.up()</code>. */ public Object up(Event evt) { return up_prot.up(evt); } /** * An event is to be sent down the stack. The layer may want to examine its type and perform some * action on it, depending on the event's type. If the event is a message MSG, then the layer may * need to add a header to it (or do nothing at all) before sending it down the stack using <code> * down_prot.down()</code>. In case of a GET_ADDRESS event (which tries to retrieve the stack's * address from one of the bottom layers), the layer may need to send a new response event back up * the stack using <code>up_prot.up()</code>. */ public Object down(Event evt) { return down_prot.down(evt); } }
public class TCPConnectionMap { private final Mapper mapper; private final InetAddress bind_addr; private final Address local_addr; // bind_addr + port of srv_sock private final ThreadGroup thread_group = new ThreadGroup(Util.getGlobalThreadGroup(), "ConnectionMap"); private final ServerSocket srv_sock; private Receiver receiver; private final long conn_expire_time; private final Log log = LogFactory.getLog(getClass()); private int recv_buf_size = 120000; private int send_buf_size = 60000; private int send_queue_size = 0; private int sock_conn_timeout = 1000; // max time in millis to wait for Socket.connect() to return private boolean tcp_nodelay = false; private int linger = -1; private final Thread acceptor; private final AtomicBoolean running = new AtomicBoolean(false); private volatile boolean use_send_queues = false; protected SocketFactory socket_factory = new DefaultSocketFactory(); public TCPConnectionMap( String service_name, ThreadFactory f, SocketFactory socket_factory, Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port) throws Exception { this(service_name, f, socket_factory, r, bind_addr, external_addr, srv_port, max_port, 0, 0); } public TCPConnectionMap( String service_name, ThreadFactory f, Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port, long reaper_interval, long conn_expire_time) throws Exception { this( service_name, f, null, r, bind_addr, external_addr, srv_port, max_port, reaper_interval, conn_expire_time); } public TCPConnectionMap( String service_name, ThreadFactory f, SocketFactory socket_factory, Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port, long reaper_interval, long conn_expire_time) throws Exception { this.mapper = new Mapper(f, reaper_interval); this.receiver = r; this.bind_addr = bind_addr; this.conn_expire_time = conn_expire_time; if (socket_factory != null) this.socket_factory = socket_factory; this.srv_sock = Util.createServerSocket(this.socket_factory, service_name, bind_addr, srv_port, max_port); if (external_addr != null) local_addr = new IpAddress(external_addr, srv_sock.getLocalPort()); else if (bind_addr != null) local_addr = new IpAddress(bind_addr, srv_sock.getLocalPort()); else local_addr = new IpAddress(srv_sock.getLocalPort()); acceptor = f.newThread(thread_group, new ConnectionAcceptor(), "ConnectionMap.Acceptor"); } public Address getLocalAddress() { return local_addr; } public Receiver getReceiver() { return receiver; } public void setReceiver(Receiver receiver) { this.receiver = receiver; } public SocketFactory getSocketFactory() { return socket_factory; } public void setSocketFactory(SocketFactory socket_factory) { this.socket_factory = socket_factory; } public void addConnectionMapListener( AbstractConnectionMap.ConnectionMapListener<TCPConnection> l) { mapper.addConnectionMapListener(l); } public void removeConnectionMapListener( AbstractConnectionMap.ConnectionMapListener<TCPConnection> l) { mapper.removeConnectionMapListener(l); } /** * Calls the receiver callback. We do not serialize access to this method, and it may be called * concurrently by several Connection handler threads. Therefore the receiver needs to be * reentrant. */ public void receive(Address sender, byte[] data, int offset, int length) { receiver.receive(sender, data, offset, length); } public void send(Address dest, byte[] data, int offset, int length) throws Exception { if (dest == null) { if (log.isErrorEnabled()) log.error("destination is null"); return; } if (data == null) { log.warn("data is null; discarding packet"); return; } if (!running.get()) { if (log.isDebugEnabled()) log.debug("connection table is not running, discarding message to " + dest); return; } if (dest.equals(local_addr)) { receive(local_addr, data, offset, length); return; } // 1. Try to obtain correct Connection (or create one if not yet existent) TCPConnection conn; conn = mapper.getConnection(dest); // 2. Send the message using that connection if (conn != null) { try { conn.send(data, offset, length); } catch (Exception ex) { mapper.removeConnection(dest); throw ex; } } } public void start() throws Exception { if (running.compareAndSet(false, true)) { acceptor.start(); mapper.start(); } } public void stop() { if (running.compareAndSet(true, false)) { try { getSocketFactory().close(srv_sock); } catch (IOException e) { } Util.interruptAndWaitToDie(acceptor); mapper.stop(); } } private void setSocketParameters(Socket client_sock) throws SocketException { if (log.isTraceEnabled()) log.trace( "[" + local_addr + "] accepted connection from " + client_sock.getInetAddress() + ":" + client_sock.getPort()); try { client_sock.setSendBufferSize(send_buf_size); } catch (IllegalArgumentException ex) { if (log.isErrorEnabled()) log.error("exception setting send buffer size to " + send_buf_size + " bytes", ex); } try { client_sock.setReceiveBufferSize(recv_buf_size); } catch (IllegalArgumentException ex) { if (log.isErrorEnabled()) log.error("exception setting receive buffer size to " + send_buf_size + " bytes", ex); } client_sock.setKeepAlive(true); client_sock.setTcpNoDelay(tcp_nodelay); if (linger > 0) client_sock.setSoLinger(true, linger); else client_sock.setSoLinger(false, -1); } /** Used for message reception. */ public interface Receiver { void receive(Address sender, byte[] data, int offset, int length); } private class ConnectionAcceptor implements Runnable { /** * Acceptor thread. Continuously accept new connections. Create a new thread for each new * connection and put it in conns. When the thread should stop, it is interrupted by the thread * creator. */ public void run() { while (!srv_sock.isClosed() && !Thread.currentThread().isInterrupted()) { TCPConnection conn = null; Socket client_sock = null; try { client_sock = srv_sock.accept(); conn = new TCPConnection(client_sock); Address peer_addr = conn.getPeerAddress(); mapper.getLock().lock(); try { boolean currentConnectionOpen = mapper.hasOpenConnection(peer_addr); boolean replaceWithNewConnection = false; if (currentConnectionOpen) { replaceWithNewConnection = peer_addr.compareTo(local_addr) > 0; } if (!currentConnectionOpen || replaceWithNewConnection) { mapper.removeConnection(peer_addr); mapper.addConnection(peer_addr, conn); conn.start(mapper.getThreadFactory()); // starts handler thread on this socket } else { Util.close(conn); } } finally { mapper.getLock().unlock(); } } catch (SocketException se) { boolean threadExiting = srv_sock.isClosed() || Thread.currentThread().isInterrupted(); if (threadExiting) { break; } else { if (log.isWarnEnabled()) log.warn("Could not accept connection from peer ", se); Util.close(conn); Util.close(client_sock); } } catch (Exception ex) { if (log.isWarnEnabled()) log.warn("Could not read accept connection from peer " + ex); Util.close(conn); Util.close(client_sock); } } if (log.isTraceEnabled()) log.trace(Thread.currentThread().getName() + " terminated"); } } public void setReceiveBufferSize(int recv_buf_size) { this.recv_buf_size = recv_buf_size; } public void setSocketConnectionTimeout(int sock_conn_timeout) { this.sock_conn_timeout = sock_conn_timeout; } public void setSendBufferSize(int send_buf_size) { this.send_buf_size = send_buf_size; } public void setLinger(int linger) { this.linger = linger; } public void setTcpNodelay(boolean tcp_nodelay) { this.tcp_nodelay = tcp_nodelay; } public void setSendQueueSize(int send_queue_size) { this.send_queue_size = send_queue_size; } public void setUseSendQueues(boolean use_send_queues) { this.use_send_queues = use_send_queues; } public int getNumConnections() { return mapper.getNumConnections(); } public boolean connectionEstablishedTo(Address addr) { return mapper.connectionEstablishedTo(addr); } public String printConnections() { return mapper.printConnections(); } public void retainAll(Collection<Address> members) { mapper.retainAll(members); } public long getConnectionExpiryTimeout() { return conn_expire_time; } public int getSenderQueueSize() { return send_queue_size; } public String toString() { StringBuilder ret = new StringBuilder(); ret.append("local_addr=" + local_addr).append("\n"); ret.append("connections (" + mapper.size() + "):\n"); ret.append(mapper.toString()); ret.append('\n'); return ret.toString(); } public class TCPConnection implements Connection { private final Socket sock; // socket to/from peer (result of srv_sock.accept() or new Socket()) private final Lock send_lock = new ReentrantLock(); // serialize send() private final Log log = LogFactory.getLog(getClass()); private final byte[] cookie = {'b', 'e', 'l', 'a'}; private final DataOutputStream out; private final DataInputStream in; private final Address peer_addr; // address of the 'other end' of the connection private final int peer_addr_read_timeout = 2000; // max time in milliseconds to block on reading peer address private long last_access = System.currentTimeMillis(); // last time a message was sent or received private Sender sender; private ConnectionPeerReceiver connectionPeerReceiver; private AtomicBoolean active = new AtomicBoolean(false); TCPConnection(Address peer_addr) throws Exception { if (peer_addr == null) throw new IllegalArgumentException("Invalid parameter peer_addr=" + peer_addr); SocketAddress destAddr = new InetSocketAddress( ((IpAddress) peer_addr).getIpAddress(), ((IpAddress) peer_addr).getPort()); this.sock = new Socket(); this.sock.bind(new InetSocketAddress(bind_addr, 0)); Util.connect(this.sock, destAddr, sock_conn_timeout); setSocketParameters(sock); this.out = new DataOutputStream(new BufferedOutputStream(sock.getOutputStream())); this.in = new DataInputStream(new BufferedInputStream(sock.getInputStream())); sendLocalAddress(getLocalAddress()); this.peer_addr = peer_addr; } TCPConnection(Socket s) throws Exception { if (s == null) throw new IllegalArgumentException("Invalid parameter s=" + s); setSocketParameters(s); this.out = new DataOutputStream(new BufferedOutputStream(s.getOutputStream())); this.in = new DataInputStream(new BufferedInputStream(s.getInputStream())); this.peer_addr = readPeerAddress(s); this.sock = s; } private Address getPeerAddress() { return peer_addr; } private void updateLastAccessed() { last_access = System.currentTimeMillis(); } private void start(ThreadFactory f) { // only start once.... if (active.compareAndSet(false, true)) { connectionPeerReceiver = new ConnectionPeerReceiver(f); connectionPeerReceiver.start(); if (isSenderUsed()) { sender = new Sender(f, getSenderQueueSize()); sender.start(); } } } private boolean isSenderUsed() { return getSenderQueueSize() > 0 && use_send_queues; } private String getSockAddress() { StringBuilder sb = new StringBuilder(); if (sock != null) { sb.append(sock.getLocalAddress().getHostAddress()).append(':').append(sock.getLocalPort()); sb.append(" - ") .append(sock.getInetAddress().getHostAddress()) .append(':') .append(sock.getPort()); } return sb.toString(); } /** * @param data Guaranteed to be non null * @param offset * @param length */ private void send(byte[] data, int offset, int length) throws Exception { if (isSenderUsed()) { // we need to copy the byte[] buffer here because the original buffer might get // changed meanwhile byte[] tmp = new byte[length]; System.arraycopy(data, offset, tmp, 0, length); sender.addToQueue(tmp); } else { _send(data, offset, length, true); } } /** * Sends data using the 'out' output stream of the socket * * @param data * @param offset * @param length * @param acquire_lock * @throws Exception */ private void _send(byte[] data, int offset, int length, boolean acquire_lock) throws Exception { if (acquire_lock) send_lock.lock(); try { doSend(data, offset, length); updateLastAccessed(); } catch (InterruptedException iex) { Thread.currentThread().interrupt(); // set interrupt flag again } finally { if (acquire_lock) send_lock.unlock(); } } private void doSend(byte[] data, int offset, int length) throws Exception { // we're using 'double-writes', sending the buffer to the destination in 2 pieces. this would // ensure that, if the peer closed the connection while we were idle, we would get an // exception. // this won't happen if we use a single write (see Stevens, ch. 5.13). out.writeInt(length); // write the length of the data buffer first Util.doubleWrite(data, offset, length, out); out.flush(); // may not be very efficient (but safe) } /** * Reads the peer's address. First a cookie has to be sent which has to match my own cookie, * otherwise the connection will be refused */ private Address readPeerAddress(Socket client_sock) throws Exception { int timeout = client_sock.getSoTimeout(); client_sock.setSoTimeout(peer_addr_read_timeout); try { // read the cookie first byte[] input_cookie = new byte[cookie.length]; in.readFully(input_cookie, 0, input_cookie.length); if (!matchCookie(input_cookie)) throw new SocketException( "ConnectionMap.Connection.readPeerAddress(): cookie read by " + getLocalAddress() + " does not match own cookie; terminating connection"); // then read the version short version = in.readShort(); if (!Version.isBinaryCompatible(version)) { if (log.isWarnEnabled()) log.warn( new StringBuilder("packet from ") .append(client_sock.getInetAddress()) .append(':') .append(client_sock.getPort()) .append(" has different version (") .append(Version.print(version)) .append(") from ours (") .append(Version.printVersion()) .append("). This may cause problems") .toString()); } Address client_peer_addr = new IpAddress(); client_peer_addr.readFrom(in); updateLastAccessed(); return client_peer_addr; } finally { client_sock.setSoTimeout(timeout); } } /** * Send the cookie first, then the our port number. If the cookie doesn't match the receiver's * cookie, the receiver will reject the connection and close it. * * @throws IOException */ private void sendLocalAddress(Address local_addr) throws IOException { // write the cookie out.write(cookie, 0, cookie.length); // write the version out.writeShort(Version.version); local_addr.writeTo(out); out.flush(); // needed ? updateLastAccessed(); } private boolean matchCookie(byte[] input) { if (input == null || input.length < cookie.length) return false; for (int i = 0; i < cookie.length; i++) if (cookie[i] != input[i]) return false; return true; } private class ConnectionPeerReceiver implements Runnable { private Thread recv; private final AtomicBoolean receiving = new AtomicBoolean(false); public ConnectionPeerReceiver(ThreadFactory f) { recv = f.newThread(this, "Connection.Receiver [" + getSockAddress() + "]"); } public void start() { if (receiving.compareAndSet(false, true)) { recv.start(); } } public void stop() { if (receiving.compareAndSet(true, false)) { recv.interrupt(); } } public boolean isRunning() { return receiving.get(); } public boolean canRun() { return isRunning() && isConnected(); } public void run() { try { while (!Thread.currentThread().isInterrupted() && canRun()) { try { int len = in.readInt(); byte[] buf = new byte[len]; in.readFully(buf, 0, len); updateLastAccessed(); receiver.receive(peer_addr, buf, 0, len); } catch (OutOfMemoryError mem_ex) { break; // continue; } catch (IOException io_ex) { break; } catch (Throwable e) { } } } finally { Util.close(TCPConnection.this); } } } private class Sender implements Runnable { final BlockingQueue<byte[]> send_queue; final Thread runner; private final AtomicBoolean running = new AtomicBoolean(false); public Sender(ThreadFactory tf, int send_queue_size) { this.runner = tf.newThread(this, "Connection.Sender [" + getSockAddress() + "]"); this.send_queue = new LinkedBlockingQueue<byte[]>(send_queue_size); } public void addToQueue(byte[] data) throws Exception { if (canRun()) send_queue.add(data); } public void start() { if (running.compareAndSet(false, true)) { runner.start(); } } public void stop() { if (running.compareAndSet(true, false)) { runner.interrupt(); } } public boolean isRunning() { return running.get(); } public boolean canRun() { return isRunning() && isConnected(); } public void run() { try { while (!Thread.currentThread().isInterrupted() && canRun()) { byte[] data = null; try { data = send_queue.take(); } catch (InterruptedException e) { // Thread.currentThread().interrupt(); break; } if (data != null) { try { _send(data, 0, data.length, false); } catch (Throwable ignored) { } } } } finally { Util.close(TCPConnection.this); } if (log.isTraceEnabled()) log.trace("TCPConnection.Sender thread terminated at " + local_addr); } } public String toString() { StringBuilder ret = new StringBuilder(); InetAddress local = null, remote = null; String local_str, remote_str; Socket tmp_sock = sock; if (tmp_sock == null) ret.append("<null socket>"); else { // since the sock variable gets set to null we want to make // make sure we make it through here without a nullpointer exception local = tmp_sock.getLocalAddress(); remote = tmp_sock.getInetAddress(); local_str = local != null ? Util.shortName(local) : "<null>"; remote_str = remote != null ? Util.shortName(remote) : "<null>"; ret.append( '<' + local_str + ':' + tmp_sock.getLocalPort() + " --> " + remote_str + ':' + tmp_sock.getPort() + "> (" + ((System.currentTimeMillis() - last_access) / 1000) + " secs old)"); } tmp_sock = null; return ret.toString(); } public boolean isExpired(long now) { return getConnectionExpiryTimeout() > 0 && now - last_access >= getConnectionExpiryTimeout(); } public boolean isConnected() { return !sock.isClosed() && sock.isConnected(); } public boolean isOpen() { return isConnected() && (!isSenderUsed() || sender.isRunning()) && (connectionPeerReceiver != null && connectionPeerReceiver.isRunning()); } public void close() throws IOException { // can close even if start was never called... send_lock.lock(); try { connectionPeerReceiver.stop(); if (isSenderUsed()) { sender.stop(); } Util.close(sock); Util.close(out); Util.close(in); } finally { send_lock.unlock(); } mapper.notifyConnectionClosed(peer_addr); } } private class Mapper extends AbstractConnectionMap<TCPConnection> { public Mapper(ThreadFactory factory) { super(factory); } public Mapper(ThreadFactory factory, long reaper_interval) { super(factory, reaper_interval); } public TCPConnection getConnection(Address dest) throws Exception { TCPConnection conn = null; getLock().lock(); try { conn = conns.get(dest); if (conn != null && conn.isOpen()) return conn; try { conn = new TCPConnection(dest); conn.start(getThreadFactory()); addConnection(dest, conn); if (log.isTraceEnabled()) log.trace("created socket to " + dest); } catch (Exception ex) { if (log.isTraceEnabled()) log.trace("failed creating connection to " + dest); } } finally { getLock().unlock(); } return conn; } public boolean connectionEstablishedTo(Address address) { lock.lock(); try { TCPConnection conn = conns.get(address); return conn != null && conn.isConnected(); } finally { lock.unlock(); } } public int size() { return conns.size(); } public String toString() { StringBuilder sb = new StringBuilder(); getLock().lock(); try { for (Map.Entry<Address, TCPConnection> entry : conns.entrySet()) { sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } return sb.toString(); } finally { getLock().unlock(); } } } }
/** * A Message encapsulates data sent to members of a group. It contains among other things the * address of the sender, the destination address, a payload (byte buffer) and a list of headers. * Headers are added by protocols on the sender side and removed by protocols on the receiver's * side. * * <p>The byte buffer can point to a reference, and we can subset it using index and length. * However, when the message is serialized, we only write the bytes between index and length. * * @author Bela Ban */ public class Message implements Streamable { protected Address dest_addr; protected Address src_addr; /** The payload */ private byte[] buf; /** The index into the payload (usually 0) */ protected int offset; /** The number of bytes in the buffer (usually buf.length is buf not equal to null). */ protected int length; /** All headers are placed here */ protected Headers headers; private volatile byte flags; private volatile byte transient_flags; // transient_flags is neither marshalled nor copied protected static final Log log = LogFactory.getLog(Message.class); static final byte DEST_SET = 1 << 0; static final byte SRC_SET = 1 << 1; static final byte BUF_SET = 1 << 2; // static final byte HDRS_SET=8; // bela July 15 2005: not needed, we always create headers // =============================== Flags ==================================== public static final byte OOB = 1 << 0; public static final byte LOOPBACK = 1 << 1; // if message was sent to self public static final byte DONT_BUNDLE = 1 << 2; // don't bundle message at the transport public static final byte NO_FC = 1 << 3; // bypass flow control public static final byte SCOPED = 1 << 4; // when a message has a scope // the following 2 flags will be removed in 3.x, when https://jira.jboss.org/browse/JGRP-1250 has // been resolved public static final byte NO_RELIABILITY = 1 << 5; // bypass UNICAST(2) and NAKACK public static final byte NO_TOTAL_ORDER = 1 << 6; // bypass total order (e.g. SEQUENCER) // =========================== Transient flags ============================== public static final byte OOB_DELIVERED = 1 << 0; // OOB which has already been delivered up the stack /** * Public constructor * * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member. * <p> */ public Message(Address dest) { setDest(dest); headers = createHeaders(3); } /** * Public constructor * * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member. * <p> * @param src Address of sender * @param buf Message to be sent. Note that this buffer must not be modified (e.g. buf[0]=0 is not * allowed), since we don't copy the contents on clopy() or clone(). */ public Message(Address dest, Address src, byte[] buf) { this(dest); setSrc(src); setBuffer(buf); } /** * Constructs a message. The index and length parameters allow to provide a <em>reference</em> to * a byte buffer, rather than a copy, and refer to a subset of the buffer. This is important when * we want to avoid copying. When the message is serialized, only the subset is serialized.<br> * <em> Note that the byte[] buffer passed as argument must not be modified. Reason: if we * retransmit the message, it would still have a ref to the original byte[] buffer passed in as * argument, and so we would retransmit a changed byte[] buffer ! </em> * * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member. * <p> * @param src Address of sender * @param buf A reference to a byte buffer * @param offset The index into the byte buffer * @param length The number of bytes to be used from <tt>buf</tt>. Both index and length are * checked for array index violations and an ArrayIndexOutOfBoundsException will be thrown if * invalid */ public Message(Address dest, Address src, byte[] buf, int offset, int length) { this(dest); setSrc(src); setBuffer(buf, offset, length); } /** * Public constructor * * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member. * <p> * @param src Address of sender * @param obj The object will be serialized into the byte buffer. <em>Object has to be * serializable </em>! The resulting buffer must not be modified (e.g. buf[0]=0 is not * allowed), since we don't copy the contents on clopy() or clone(). * <p>Note that this is a convenience method and JGroups will use default Java serialization * to serialize <code>obj</code> into a byte buffer. */ public Message(Address dest, Address src, Serializable obj) { this(dest); setSrc(src); setObject(obj); } public Message() { headers = createHeaders(3); } public Message(boolean create_headers) { if (create_headers) headers = createHeaders(3); } public Address getDest() { return dest_addr; } public void setDest(Address new_dest) { dest_addr = new_dest; } public Address getSrc() { return src_addr; } public void setSrc(Address new_src) { src_addr = new_src; } /** * Returns a <em>reference</em> to the payload (byte buffer). Note that this buffer should not be * modified as we do not copy the buffer on copy() or clone(): the buffer of the copied message is * simply a reference to the old buffer.<br> * Even if offset and length are used: we return the <em>entire</em> buffer, not a subset. */ public byte[] getRawBuffer() { return buf; } /** * Returns a copy of the buffer if offset and length are used, otherwise a reference. * * @return byte array with a copy of the buffer. */ public final byte[] getBuffer() { if (buf == null) return null; if (offset == 0 && length == buf.length) return buf; else { byte[] retval = new byte[length]; System.arraycopy(buf, offset, retval, 0, length); return retval; } } public final void setBuffer(byte[] b) { buf = b; if (buf != null) { offset = 0; length = buf.length; } else { offset = length = 0; } } /** * Set the internal buffer to point to a subset of a given buffer * * @param b The reference to a given buffer. If null, we'll reset the buffer to null * @param offset The initial position * @param length The number of bytes */ public final void setBuffer(byte[] b, int offset, int length) { buf = b; if (buf != null) { if (offset < 0 || offset > buf.length) throw new ArrayIndexOutOfBoundsException(offset); if ((offset + length) > buf.length) throw new ArrayIndexOutOfBoundsException((offset + length)); this.offset = offset; this.length = length; } else { this.offset = this.length = 0; } } /** * <em> Note that the byte[] buffer passed as argument must not be modified. Reason: if we * retransmit the message, it would still have a ref to the original byte[] buffer passed in as * argument, and so we would retransmit a changed byte[] buffer ! </em> */ public final void setBuffer(Buffer buf) { if (buf != null) { this.buf = buf.getBuf(); this.offset = buf.getOffset(); this.length = buf.getLength(); } } /** Returns the offset into the buffer at which the data starts */ public int getOffset() { return offset; } /** Returns the number of bytes in the buffer */ public int getLength() { return length; } /** * Returns a reference to the headers hashmap, which is <em>immutable</em>. Any attempt to modify * the returned map will cause a runtime exception */ public Map<Short, Header> getHeaders() { return headers.getHeaders(); } public String printHeaders() { return headers.printHeaders(); } public int getNumHeaders() { return headers.size(); } /** * Takes an object and uses Java serialization to generate the byte[] buffer which is set in the * message. */ public final void setObject(Serializable obj) { if (obj == null) return; try { byte[] tmp = Util.objectToByteBuffer(obj); setBuffer(tmp); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } /** * Uses Java serialization to create an object from the buffer of the message. Note that this is * dangerous when using your own classloader, e.g. inside of an application server ! Most likely, * JGroups will use the system classloader to deserialize the buffer into an object, whereas (for * example) a web application will want to use the webapp's classloader, resulting in a * ClassCastException. The recommended way is for the application to use their own serialization * and only pass byte[] buffer to JGroups. * * @return */ public final Object getObject() { try { return Util.objectFromByteBuffer(buf, offset, length); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } public void setFlag(byte flag) { if (flag > Byte.MAX_VALUE || flag < 0) throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); flags |= flag; } public void clearFlag(byte flag) { if (flag > Byte.MAX_VALUE || flag < 0) throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); flags &= ~flag; } public boolean isFlagSet(byte flag) { return isFlagSet(flags, flag); } /** * Same as {@link #setFlag(byte)} but transient flags are never marshalled * * @param flag */ public void setTransientFlag(byte flag) { if (flag > Byte.MAX_VALUE || flag < 0) throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); transient_flags |= flag; } /** * Atomically checks if a given flag is set and - if not - sets it. When multiple threads * concurrently call this method with the same flag, only one of them will be able to set the flag * * @param flag * @return True if the flag could be set, false if not (was already set) */ public boolean setTransientFlagIfAbsent(byte flag) { if (flag > Byte.MAX_VALUE || flag < 0) throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); synchronized (this) { if (isTransientFlagSet(flag)) return false; else setTransientFlag(flag); return true; } } public void clearTransientFlag(byte flag) { if (flag > Byte.MAX_VALUE || flag < 0) throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); transient_flags &= ~flag; } public boolean isTransientFlagSet(byte flag) { return isFlagSet(transient_flags, flag); } protected static boolean isFlagSet(byte flags, byte flag) { return (flags & flag) == flag; } public byte getFlags() { return flags; } public byte getTransientFlags() { return transient_flags; } public void setScope(short scope) { Util.setScope(this, scope); } public short getScope() { return Util.getScope(this); } /*---------------------- Used by protocol layers ----------------------*/ /** Puts a header given an ID into the hashmap. Overwrites potential existing entry. */ public void putHeader(short id, Header hdr) { if (id < 0) throw new IllegalArgumentException("An ID of " + id + " is invalid"); headers.putHeader(id, hdr); } /** * Puts a header given a key into the map, only if the key doesn't exist yet * * @param id * @param hdr * @return the previous value associated with the specified key, or <tt>null</tt> if there was no * mapping for the key. (A <tt>null</tt> return can also indicate that the map previously * associated <tt>null</tt> with the key, if the implementation supports null values.) */ public Header putHeaderIfAbsent(short id, Header hdr) { if (id <= 0) throw new IllegalArgumentException("An ID of " + id + " is invalid"); return headers.putHeaderIfAbsent(id, hdr); } /** * @param key * @return the header associated with key * @deprecated Use getHeader() instead. The issue with removing a header is described in * http://jira.jboss.com/jira/browse/JGRP-393 */ public Header removeHeader(short id) { return getHeader(id); } public Header getHeader(short id) { if (id <= 0) throw new IllegalArgumentException("An ID of " + id + " is invalid"); return headers.getHeader(id); } /*---------------------------------------------------------------------*/ public Message copy() { return copy(true); } /** * Create a copy of the message. If offset and length are used (to refer to another buffer), the * copy will contain only the subset offset and length point to, copying the subset into the new * copy. * * @param copy_buffer * @return Message with specified data */ public Message copy(boolean copy_buffer) { return copy(copy_buffer, true); } /** * Create a copy of the message. If offset and length are used (to refer to another buffer), the * copy will contain only the subset offset and length point to, copying the subset into the new * copy. * * @param copy_buffer * @param copy_headers Copy the headers * @return Message with specified data */ public Message copy(boolean copy_buffer, boolean copy_headers) { Message retval = new Message(false); retval.dest_addr = dest_addr; retval.src_addr = src_addr; retval.flags = flags; if (copy_buffer && buf != null) { // change bela Feb 26 2004: we don't resolve the reference retval.setBuffer(buf, offset, length); } retval.headers = copy_headers ? createHeaders(headers) : createHeaders(3); return retval; } protected Object clone() throws CloneNotSupportedException { return copy(); } public Message makeReply() { return new Message(src_addr); } public String toString() { StringBuilder ret = new StringBuilder(64); ret.append("[dst: "); if (dest_addr == null) ret.append("<null>"); else ret.append(dest_addr); ret.append(", src: "); if (src_addr == null) ret.append("<null>"); else ret.append(src_addr); int size; if ((size = getNumHeaders()) > 0) ret.append(" (").append(size).append(" headers)"); ret.append(", size="); if (buf != null && length > 0) ret.append(length); else ret.append('0'); ret.append(" bytes"); if (flags > 0) ret.append(", flags=").append(flagsToString(flags)); if (transient_flags > 0) ret.append(", transient_flags=" + transientFlagsToString(transient_flags)); ret.append(']'); return ret.toString(); } /** Tries to read an object from the message's buffer and prints it */ public String toStringAsObject() { if (buf == null) return null; try { Object obj = getObject(); return obj != null ? obj.toString() : ""; } catch (Exception e) { // it is not an object return ""; } } public String printObjectHeaders() { return headers.printObjectHeaders(); } /* ----------------------------------- Interface Streamable ------------------------------- */ /** * Streams all members (dest and src addresses, buffer and headers) to the output stream. * * @param out * @throws IOException */ public void writeTo(DataOutputStream out) throws IOException { byte leading = 0; if (dest_addr != null) leading = Util.setFlag(leading, DEST_SET); if (src_addr != null) leading = Util.setFlag(leading, SRC_SET); if (buf != null) leading = Util.setFlag(leading, BUF_SET); // 1. write the leading byte first out.write(leading); // 2. the flags (e.g. OOB, LOW_PRIO) out.write(flags); // 3. dest_addr if (dest_addr != null) Util.writeAddress(dest_addr, out); // 4. src_addr if (src_addr != null) Util.writeAddress(src_addr, out); // 5. buf if (buf != null) { out.writeInt(length); out.write(buf, offset, length); } // 6. headers int size = headers.size(); out.writeShort(size); final short[] ids = headers.getRawIDs(); final Header[] hdrs = headers.getRawHeaders(); for (int i = 0; i < ids.length; i++) { if (ids[i] > 0) { out.writeShort(ids[i]); writeHeader(hdrs[i], out); } } } /** * Writes the message to the output stream, but excludes the dest and src addresses unless the src * address given as argument is different from the message's src address * * @param src * @param out * @throws IOException */ public void writeToNoAddrs(Address src, DataOutputStream out) throws IOException { byte leading = 0; boolean write_src_addr = src == null || src_addr != null && !src_addr.equals(src); if (write_src_addr) leading = Util.setFlag(leading, SRC_SET); if (buf != null) leading = Util.setFlag(leading, BUF_SET); // 1. write the leading byte first out.write(leading); // 2. the flags (e.g. OOB, LOW_PRIO) out.write(flags); // 4. src_addr if (write_src_addr) Util.writeAddress(src_addr, out); // 5. buf if (buf != null) { out.writeInt(length); out.write(buf, offset, length); } // 6. headers int size = headers.size(); out.writeShort(size); final short[] ids = headers.getRawIDs(); final Header[] hdrs = headers.getRawHeaders(); for (int i = 0; i < ids.length; i++) { if (ids[i] > 0) { out.writeShort(ids[i]); writeHeader(hdrs[i], out); } } } public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { // 1. read the leading byte first byte leading = in.readByte(); // 2. the flags flags = in.readByte(); // 3. dest_addr if (Util.isFlagSet(leading, DEST_SET)) dest_addr = Util.readAddress(in); // 4. src_addr if (Util.isFlagSet(leading, SRC_SET)) src_addr = Util.readAddress(in); // 5. buf if (Util.isFlagSet(leading, BUF_SET)) { int len = in.readInt(); buf = new byte[len]; in.readFully(buf, 0, len); length = len; } // 6. headers int len = in.readShort(); headers = createHeaders(len); short[] ids = headers.getRawIDs(); Header[] hdrs = headers.getRawHeaders(); for (int i = 0; i < len; i++) { short id = in.readShort(); Header hdr = readHeader(in); ids[i] = id; hdrs[i] = hdr; } } /* --------------------------------- End of Interface Streamable ----------------------------- */ /** * Returns the exact size of the marshalled message. Uses method size() of each header to compute * the size, so if a Header subclass doesn't implement size() we will use an approximation. * However, most relevant header subclasses have size() implemented correctly. (See * org.jgroups.tests.SizeTest). * * @return The number of bytes for the marshalled message */ public long size() { long retval = Global.BYTE_SIZE // leading byte + Global.BYTE_SIZE; // flags if (dest_addr != null) retval += Util.size(dest_addr); if (src_addr != null) retval += Util.size(src_addr); if (buf != null) retval += Global.INT_SIZE // length (integer) + length; // number of bytes in the buffer retval += Global.SHORT_SIZE; // number of headers retval += headers.marshalledSize(); return retval; } /* ----------------------------------- Private methods ------------------------------- */ public static String flagsToString(byte flags) { StringBuilder sb = new StringBuilder(); boolean first = true; if (isFlagSet(flags, OOB)) { first = false; sb.append("OOB"); } if (isFlagSet(flags, LOOPBACK)) { if (!first) sb.append("|"); else first = false; sb.append("LOOPBACK"); } if (isFlagSet(flags, DONT_BUNDLE)) { if (!first) sb.append("|"); else first = false; sb.append("DONT_BUNDLE"); } if (isFlagSet(flags, NO_FC)) { if (!first) sb.append("|"); else first = false; sb.append("NO_FC"); } if (isFlagSet(flags, SCOPED)) { if (!first) sb.append("|"); else first = false; sb.append("SCOPED"); } if (isFlagSet(flags, NO_RELIABILITY)) { if (!first) sb.append("|"); else first = false; sb.append("NO_RELIABILITY"); } if (isFlagSet(flags, NO_TOTAL_ORDER)) { if (!first) sb.append("|"); else first = false; sb.append("NO_TOTAL_ORDER"); } return sb.toString(); } public static String transientFlagsToString(byte flags) { StringBuilder sb = new StringBuilder(); boolean first = true; if (isFlagSet(flags, OOB_DELIVERED)) { if (!first) sb.append("|"); else first = false; sb.append("OOB_DELIVERED"); } return sb.toString(); } private static void writeHeader(Header hdr, DataOutputStream out) throws IOException { short magic_number = ClassConfigurator.getMagicNumber(hdr.getClass()); out.writeShort(magic_number); hdr.writeTo(out); } private static Header readHeader(DataInputStream in) throws IOException { try { short magic_number = in.readShort(); Class clazz = ClassConfigurator.get(magic_number); if (clazz == null) throw new IllegalArgumentException( "magic number " + magic_number + " is not available in magic map"); Header hdr = (Header) clazz.newInstance(); hdr.readFrom(in); return hdr; } catch (Exception ex) { IOException io_ex = new IOException("failed reading header"); io_ex.initCause(ex); throw io_ex; } } private static Headers createHeaders(int size) { return size > 0 ? new Headers(size) : new Headers(3); } private static Headers createHeaders(Headers m) { return new Headers(m); } /* ------------------------------- End of Private methods ---------------------------- */ }
public class TCPConnection implements Connection { private final Socket sock; // socket to/from peer (result of srv_sock.accept() or new Socket()) private final Lock send_lock = new ReentrantLock(); // serialize send() private final Log log = LogFactory.getLog(getClass()); private final byte[] cookie = {'b', 'e', 'l', 'a'}; private final DataOutputStream out; private final DataInputStream in; private final Address peer_addr; // address of the 'other end' of the connection private final int peer_addr_read_timeout = 2000; // max time in milliseconds to block on reading peer address private long last_access = System.currentTimeMillis(); // last time a message was sent or received private Sender sender; private ConnectionPeerReceiver connectionPeerReceiver; private AtomicBoolean active = new AtomicBoolean(false); TCPConnection(Address peer_addr) throws Exception { if (peer_addr == null) throw new IllegalArgumentException("Invalid parameter peer_addr=" + peer_addr); SocketAddress destAddr = new InetSocketAddress( ((IpAddress) peer_addr).getIpAddress(), ((IpAddress) peer_addr).getPort()); this.sock = new Socket(); this.sock.bind(new InetSocketAddress(bind_addr, 0)); Util.connect(this.sock, destAddr, sock_conn_timeout); setSocketParameters(sock); this.out = new DataOutputStream(new BufferedOutputStream(sock.getOutputStream())); this.in = new DataInputStream(new BufferedInputStream(sock.getInputStream())); sendLocalAddress(getLocalAddress()); this.peer_addr = peer_addr; } TCPConnection(Socket s) throws Exception { if (s == null) throw new IllegalArgumentException("Invalid parameter s=" + s); setSocketParameters(s); this.out = new DataOutputStream(new BufferedOutputStream(s.getOutputStream())); this.in = new DataInputStream(new BufferedInputStream(s.getInputStream())); this.peer_addr = readPeerAddress(s); this.sock = s; } private Address getPeerAddress() { return peer_addr; } private void updateLastAccessed() { last_access = System.currentTimeMillis(); } private void start(ThreadFactory f) { // only start once.... if (active.compareAndSet(false, true)) { connectionPeerReceiver = new ConnectionPeerReceiver(f); connectionPeerReceiver.start(); if (isSenderUsed()) { sender = new Sender(f, getSenderQueueSize()); sender.start(); } } } private boolean isSenderUsed() { return getSenderQueueSize() > 0 && use_send_queues; } private String getSockAddress() { StringBuilder sb = new StringBuilder(); if (sock != null) { sb.append(sock.getLocalAddress().getHostAddress()).append(':').append(sock.getLocalPort()); sb.append(" - ") .append(sock.getInetAddress().getHostAddress()) .append(':') .append(sock.getPort()); } return sb.toString(); } /** * @param data Guaranteed to be non null * @param offset * @param length */ private void send(byte[] data, int offset, int length) throws Exception { if (isSenderUsed()) { // we need to copy the byte[] buffer here because the original buffer might get // changed meanwhile byte[] tmp = new byte[length]; System.arraycopy(data, offset, tmp, 0, length); sender.addToQueue(tmp); } else { _send(data, offset, length, true); } } /** * Sends data using the 'out' output stream of the socket * * @param data * @param offset * @param length * @param acquire_lock * @throws Exception */ private void _send(byte[] data, int offset, int length, boolean acquire_lock) throws Exception { if (acquire_lock) send_lock.lock(); try { doSend(data, offset, length); updateLastAccessed(); } catch (InterruptedException iex) { Thread.currentThread().interrupt(); // set interrupt flag again } finally { if (acquire_lock) send_lock.unlock(); } } private void doSend(byte[] data, int offset, int length) throws Exception { // we're using 'double-writes', sending the buffer to the destination in 2 pieces. this would // ensure that, if the peer closed the connection while we were idle, we would get an // exception. // this won't happen if we use a single write (see Stevens, ch. 5.13). out.writeInt(length); // write the length of the data buffer first Util.doubleWrite(data, offset, length, out); out.flush(); // may not be very efficient (but safe) } /** * Reads the peer's address. First a cookie has to be sent which has to match my own cookie, * otherwise the connection will be refused */ private Address readPeerAddress(Socket client_sock) throws Exception { int timeout = client_sock.getSoTimeout(); client_sock.setSoTimeout(peer_addr_read_timeout); try { // read the cookie first byte[] input_cookie = new byte[cookie.length]; in.readFully(input_cookie, 0, input_cookie.length); if (!matchCookie(input_cookie)) throw new SocketException( "ConnectionMap.Connection.readPeerAddress(): cookie read by " + getLocalAddress() + " does not match own cookie; terminating connection"); // then read the version short version = in.readShort(); if (!Version.isBinaryCompatible(version)) { if (log.isWarnEnabled()) log.warn( new StringBuilder("packet from ") .append(client_sock.getInetAddress()) .append(':') .append(client_sock.getPort()) .append(" has different version (") .append(Version.print(version)) .append(") from ours (") .append(Version.printVersion()) .append("). This may cause problems") .toString()); } Address client_peer_addr = new IpAddress(); client_peer_addr.readFrom(in); updateLastAccessed(); return client_peer_addr; } finally { client_sock.setSoTimeout(timeout); } } /** * Send the cookie first, then the our port number. If the cookie doesn't match the receiver's * cookie, the receiver will reject the connection and close it. * * @throws IOException */ private void sendLocalAddress(Address local_addr) throws IOException { // write the cookie out.write(cookie, 0, cookie.length); // write the version out.writeShort(Version.version); local_addr.writeTo(out); out.flush(); // needed ? updateLastAccessed(); } private boolean matchCookie(byte[] input) { if (input == null || input.length < cookie.length) return false; for (int i = 0; i < cookie.length; i++) if (cookie[i] != input[i]) return false; return true; } private class ConnectionPeerReceiver implements Runnable { private Thread recv; private final AtomicBoolean receiving = new AtomicBoolean(false); public ConnectionPeerReceiver(ThreadFactory f) { recv = f.newThread(this, "Connection.Receiver [" + getSockAddress() + "]"); } public void start() { if (receiving.compareAndSet(false, true)) { recv.start(); } } public void stop() { if (receiving.compareAndSet(true, false)) { recv.interrupt(); } } public boolean isRunning() { return receiving.get(); } public boolean canRun() { return isRunning() && isConnected(); } public void run() { try { while (!Thread.currentThread().isInterrupted() && canRun()) { try { int len = in.readInt(); byte[] buf = new byte[len]; in.readFully(buf, 0, len); updateLastAccessed(); receiver.receive(peer_addr, buf, 0, len); } catch (OutOfMemoryError mem_ex) { break; // continue; } catch (IOException io_ex) { break; } catch (Throwable e) { } } } finally { Util.close(TCPConnection.this); } } } private class Sender implements Runnable { final BlockingQueue<byte[]> send_queue; final Thread runner; private final AtomicBoolean running = new AtomicBoolean(false); public Sender(ThreadFactory tf, int send_queue_size) { this.runner = tf.newThread(this, "Connection.Sender [" + getSockAddress() + "]"); this.send_queue = new LinkedBlockingQueue<byte[]>(send_queue_size); } public void addToQueue(byte[] data) throws Exception { if (canRun()) send_queue.add(data); } public void start() { if (running.compareAndSet(false, true)) { runner.start(); } } public void stop() { if (running.compareAndSet(true, false)) { runner.interrupt(); } } public boolean isRunning() { return running.get(); } public boolean canRun() { return isRunning() && isConnected(); } public void run() { try { while (!Thread.currentThread().isInterrupted() && canRun()) { byte[] data = null; try { data = send_queue.take(); } catch (InterruptedException e) { // Thread.currentThread().interrupt(); break; } if (data != null) { try { _send(data, 0, data.length, false); } catch (Throwable ignored) { } } } } finally { Util.close(TCPConnection.this); } if (log.isTraceEnabled()) log.trace("TCPConnection.Sender thread terminated at " + local_addr); } } public String toString() { StringBuilder ret = new StringBuilder(); InetAddress local = null, remote = null; String local_str, remote_str; Socket tmp_sock = sock; if (tmp_sock == null) ret.append("<null socket>"); else { // since the sock variable gets set to null we want to make // make sure we make it through here without a nullpointer exception local = tmp_sock.getLocalAddress(); remote = tmp_sock.getInetAddress(); local_str = local != null ? Util.shortName(local) : "<null>"; remote_str = remote != null ? Util.shortName(remote) : "<null>"; ret.append( '<' + local_str + ':' + tmp_sock.getLocalPort() + " --> " + remote_str + ':' + tmp_sock.getPort() + "> (" + ((System.currentTimeMillis() - last_access) / 1000) + " secs old)"); } tmp_sock = null; return ret.toString(); } public boolean isExpired(long now) { return getConnectionExpiryTimeout() > 0 && now - last_access >= getConnectionExpiryTimeout(); } public boolean isConnected() { return !sock.isClosed() && sock.isConnected(); } public boolean isOpen() { return isConnected() && (!isSenderUsed() || sender.isRunning()) && (connectionPeerReceiver != null && connectionPeerReceiver.isRunning()); } public void close() throws IOException { // can close even if start was never called... send_lock.lock(); try { connectionPeerReceiver.stop(); if (isSenderUsed()) { sender.stop(); } Util.close(sock); Util.close(out); Util.close(in); } finally { send_lock.unlock(); } mapper.notifyConnectionClosed(peer_addr); } }
/** * Implementation of {@link org.jgroups.util.TimeScheduler}. Uses a thread pool and a single thread * which waits for the next task to be executed. When ready, it passes the task to the associated * pool to get executed. When multiple tasks are scheduled to get executed at the same time, they're * collected in a queue associated with the task execution time, and are executed together. * * @author Bela Ban */ public class TimeScheduler2 implements TimeScheduler, Runnable { private final ThreadManagerThreadPoolExecutor pool; private final ConcurrentSkipListMap<Long, Entry> tasks = new ConcurrentSkipListMap<Long, Entry>(); private Thread runner = null; private final Lock lock = new ReentrantLock(); private final Condition tasks_available = lock.newCondition(); @GuardedBy("lock") private long next_execution_time = 0; /** Needed to signal going from 0 tasks to non-zero (we cannot use tasks.isEmpty() here ...) */ protected final AtomicBoolean no_tasks = new AtomicBoolean(true); protected volatile boolean running = false; protected static final Log log = LogFactory.getLog(TimeScheduler2.class); protected ThreadDecorator threadDecorator = null; protected ThreadFactory timer_thread_factory = null; protected static final long SLEEP_TIME = 10000; /** Create a scheduler that executes tasks in dynamically adjustable intervals */ public TimeScheduler2() { pool = new ThreadManagerThreadPoolExecutor( 4, 10, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(5000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); init(); } public TimeScheduler2( ThreadFactory factory, int min_threads, int max_threads, long keep_alive_time, int max_queue_size) { timer_thread_factory = factory; pool = new ThreadManagerThreadPoolExecutor( min_threads, max_threads, keep_alive_time, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(max_queue_size), factory, new ThreadPoolExecutor.CallerRunsPolicy()); init(); } public TimeScheduler2(int corePoolSize) { pool = new ThreadManagerThreadPoolExecutor( corePoolSize, corePoolSize * 2, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(5000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); init(); } public ThreadDecorator getThreadDecorator() { return threadDecorator; } public void setThreadDecorator(ThreadDecorator threadDecorator) { this.threadDecorator = threadDecorator; pool.setThreadDecorator(threadDecorator); } public void setThreadFactory(ThreadFactory factory) { pool.setThreadFactory(factory); } public int getMinThreads() { return pool.getCorePoolSize(); } public void setMinThreads(int size) { pool.setCorePoolSize(size); } public int getMaxThreads() { return pool.getMaximumPoolSize(); } public void setMaxThreads(int size) { pool.setMaximumPoolSize(size); } public long getKeepAliveTime() { return pool.getKeepAliveTime(TimeUnit.MILLISECONDS); } public void setKeepAliveTime(long time) { pool.setKeepAliveTime(time, TimeUnit.MILLISECONDS); } public int getCurrentThreads() { return pool.getPoolSize(); } public int getQueueSize() { return pool.getQueue().size(); } public String dumpTimerTasks() { StringBuilder sb = new StringBuilder(); for (Entry entry : tasks.values()) { sb.append(entry.dump()).append("\n"); } return sb.toString(); } public void execute(Runnable task) { schedule(task, 0, TimeUnit.MILLISECONDS); } public Future<?> schedule(Runnable work, long delay, TimeUnit unit) { if (work == null) return null; Future<?> retval = null; long key = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(delay, unit); // execution time Entry task = new Entry(work); while (!isShutdown()) { Entry existing = tasks.putIfAbsent(key, task); if (existing == null) { retval = task.getFuture(); break; // break out of the while loop } if ((retval = existing.add(work)) != null) break; } if (key < next_execution_time || no_tasks.compareAndSet(true, false)) { if (key >= next_execution_time) key = 0L; taskReady(key); } return retval; } public Future<?> scheduleWithFixedDelay( Runnable task, long initial_delay, long delay, TimeUnit unit) { if (task == null) throw new NullPointerException(); if (isShutdown()) return null; RecurringTask wrapper = new FixedIntervalTask(task, delay); wrapper.doSchedule(initial_delay); return wrapper; } public Future<?> scheduleAtFixedRate( Runnable task, long initial_delay, long delay, TimeUnit unit) { if (task == null) throw new NullPointerException(); if (isShutdown()) return null; RecurringTask wrapper = new FixedRateTask(task, delay); wrapper.doSchedule(initial_delay); return wrapper; } /** * Schedule a task for execution at varying intervals. After execution, the task will get * rescheduled after {@link org.jgroups.util.TimeScheduler2.Task#nextInterval()} milliseconds. The * task is neve done until nextInterval() return a value <= 0 or the task is cancelled. * * @param task the task to execute Task is rescheduled relative to the last time it * <i>actually</i> started execution * <p><tt>false</tt>:<br> * Task is scheduled relative to its <i>last</i> execution schedule. This has the effect that * the time between two consecutive executions of the task remains the same. * <p>Note that relative is always true; we always schedule the next execution relative to the * last *actual* */ public Future<?> scheduleWithDynamicInterval(Task task) { if (task == null) throw new NullPointerException(); if (isShutdown()) return null; RecurringTask task_wrapper = new DynamicIntervalTask(task); task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor return task_wrapper; } /** * Returns the number of tasks currently in the timer * * @return The number of tasks currently in the timer */ public int size() { int retval = 0; Collection<Entry> values = tasks.values(); for (Entry entry : values) retval += entry.size(); return retval; } public String toString() { return getClass().getSimpleName(); } /** * Stops the timer, cancelling all tasks * * @throws InterruptedException if interrupted while waiting for thread to return */ public void stop() { stopRunner(); java.util.List<Runnable> remaining_tasks = pool.shutdownNow(); for (Runnable task : remaining_tasks) { if (task instanceof Future) { Future future = (Future) task; future.cancel(true); } } pool.getQueue().clear(); try { pool.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { } for (Entry entry : tasks.values()) entry.cancel(); tasks.clear(); } public boolean isShutdown() { return pool.isShutdown(); } public void run() { while (running) { try { _run(); } catch (Throwable t) { log.error("failed executing tasks(s)", t); } } } protected void _run() { ConcurrentNavigableMap<Long, Entry> head_map; // head_map = entries which are <= curr time (ready to be executed) if (!(head_map = tasks.headMap(System.currentTimeMillis(), true)).isEmpty()) { final List<Long> keys = new LinkedList<Long>(); for (Map.Entry<Long, Entry> entry : head_map.entrySet()) { final Long key = entry.getKey(); final Entry val = entry.getValue(); pool.execute( new Runnable() { public void run() { val.execute(); } }); keys.add(key); } tasks.keySet().removeAll(keys); } if (tasks.isEmpty()) { no_tasks.compareAndSet(false, true); waitFor(); // sleeps until time elapses, or a task with a lower execution time is added } else waitUntilNextExecution(); // waits until next execution, or a task with a lower execution time // is added } protected void init() { if (threadDecorator != null) pool.setThreadDecorator(threadDecorator); // pool.allowCoreThreadTimeOut(true); startRunner(); } /** Sleeps until the next task in line is ready to be executed */ protected void waitUntilNextExecution() { lock.lock(); try { if (!running) return; next_execution_time = tasks.firstKey(); long sleep_time = next_execution_time - System.currentTimeMillis(); tasks_available.await(sleep_time, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { } finally { lock.unlock(); } } protected void waitFor() { lock.lock(); try { if (!running) return; tasks_available.await(SLEEP_TIME, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { } finally { lock.unlock(); } } /** Signals that a task with a lower execution time than next_execution_time is ready */ protected void taskReady(long trigger_time) { lock.lock(); try { if (trigger_time > 0) next_execution_time = trigger_time; tasks_available.signal(); } finally { lock.unlock(); } } protected void startRunner() { running = true; runner = timer_thread_factory != null ? timer_thread_factory.newThread(this, "Timer runner") : new Thread(this, "Timer runner"); runner.start(); } protected void stopRunner() { lock.lock(); try { running = false; tasks_available.signal(); } finally { lock.unlock(); } } private static class Entry { private final MyTask task; // the task (wrapper) to execute private MyTask last; // points to the last task private final Lock lock = new ReentrantLock(); @GuardedBy("lock") private boolean completed = false; // set to true when the task has been executed public Entry(Runnable task) { last = this.task = new MyTask(task); } Future<?> getFuture() { return task; } Future<?> add(Runnable task) { lock.lock(); try { if (completed) return null; MyTask retval = new MyTask(task); last.next = retval; last = last.next; return retval; } finally { lock.unlock(); } } void execute() { lock.lock(); try { if (completed) return; completed = true; for (MyTask tmp = task; tmp != null; tmp = tmp.next) { if (!(tmp.isCancelled() || tmp.isDone())) { try { tmp.run(); } catch (Throwable t) { log.error("task execution failed", t); } finally { tmp.done = true; } } } } finally { lock.unlock(); } } void cancel() { lock.lock(); try { if (completed) return; for (MyTask tmp = task; tmp != null; tmp = tmp.next) tmp.cancel(true); } finally { lock.unlock(); } } int size() { int retval = 1; for (MyTask tmp = task.next; tmp != null; tmp = tmp.next) retval++; return retval; } public String toString() { return size() + " tasks"; } public String dump() { StringBuilder sb = new StringBuilder(); boolean first = true; for (MyTask tmp = task; tmp != null; tmp = tmp.next) { if (!first) sb.append(", "); else first = false; sb.append(tmp); } return sb.toString(); } } /** Simple task wrapper, always executed by at most 1 thread. */ protected static class MyTask implements Future, Runnable { protected final Runnable task; protected volatile boolean cancelled = false; protected volatile boolean done = false; protected MyTask next; public MyTask(Runnable task) { this.task = task; } public boolean cancel(boolean mayInterruptIfRunning) { boolean retval = !isDone(); cancelled = true; return retval; } public boolean isCancelled() { return cancelled; } public boolean isDone() { return done || cancelled; } public Object get() throws InterruptedException, ExecutionException { return null; } public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } public void run() { if (isDone()) return; try { task.run(); } catch (Throwable t) { log.error("failed executing task " + task, t); } finally { done = true; } } public String toString() { return task.toString(); } } /** * Task which executes multiple times. An instance of this class wraps the real task and * intercepts run(): when called, it forwards the call to task.run() and then schedules another * execution (until cancelled). The {@link #nextInterval()} method determines the time to wait * until the next execution. * * @param <V> */ private abstract class RecurringTask<V> implements Runnable, Future<V> { protected final Runnable task; protected volatile Future<?> future; // cannot be null ! protected volatile boolean cancelled = false; public RecurringTask(Runnable task) { this.task = task; } /** * The time to wait until the next execution * * @return Number of milliseconds to wait until the next execution is scheduled */ protected abstract long nextInterval(); protected boolean rescheduleOnZeroDelay() { return false; } public void doSchedule() { long next_interval = nextInterval(); if (next_interval <= 0 && !rescheduleOnZeroDelay()) { if (log.isTraceEnabled()) log.trace("task will not get rescheduled as interval is " + next_interval); return; } future = schedule(this, next_interval, TimeUnit.MILLISECONDS); if (cancelled) future.cancel(true); } public void doSchedule(long next_interval) { future = schedule(this, next_interval, TimeUnit.MILLISECONDS); if (cancelled) future.cancel(true); } public void run() { if (cancelled) { if (future != null) future.cancel(true); return; } try { task.run(); } catch (Throwable t) { log.error("failed running task " + task, t); } if (!cancelled) doSchedule(); } public boolean cancel(boolean mayInterruptIfRunning) { boolean retval = !isDone(); cancelled = true; if (future != null) future.cancel(mayInterruptIfRunning); return retval; } public boolean isCancelled() { return cancelled; } public boolean isDone() { return cancelled || (future == null || future.isDone()); } public V get() throws InterruptedException, ExecutionException { return null; } public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName() + ": task=" + task + ", cancelled=" + isCancelled()); return sb.toString(); } } private class FixedIntervalTask<V> extends RecurringTask<V> { final long interval; public FixedIntervalTask(Runnable task, long interval) { super(task); this.interval = interval; } protected long nextInterval() { return interval; } } private class FixedRateTask<V> extends RecurringTask<V> { final long interval; final long first_execution; int num_executions = 0; public FixedRateTask(Runnable task, long interval) { super(task); this.interval = interval; this.first_execution = System.currentTimeMillis(); } protected long nextInterval() { long target_time = first_execution + (interval * ++num_executions); return target_time - System.currentTimeMillis(); } protected boolean rescheduleOnZeroDelay() { return true; } } private class DynamicIntervalTask<V> extends RecurringTask<V> { public DynamicIntervalTask(Task task) { super(task); } protected long nextInterval() { if (task instanceof Task) return ((Task) task).nextInterval(); return 0; } } }