
org.elasticsearch.plugin.discovery.multicast.MulticastChannel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of discovery-multicast Show documentation
Show all versions of discovery-multicast Show documentation
The Multicast Discovery plugin allows discovery other nodes using multicast requests
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugin.discovery.multicast;
import com.google.common.collect.Maps;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.Settings;
import java.io.Closeable;
import java.net.*;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.elasticsearch.common.util.concurrent.EsExecutors.daemonThreadFactory;
/**
* A multicast channel that supports registering for receive events, and sending datagram packets. Allows
* to easily share the same multicast socket if it holds the same config.
*/
abstract class MulticastChannel implements Closeable {
/**
* Builds a channel based on the provided config, allowing to control if sharing a channel that uses
* the same config is allowed or not.
*/
public static MulticastChannel getChannel(String name, boolean shared, Config config, Listener listener) throws Exception {
if (!shared) {
return new Plain(listener, name, config);
}
return Shared.getSharedChannel(listener, config);
}
/**
* Config of multicast channel.
*/
public static final class Config {
public final int port;
public final String group;
public final int bufferSize;
public final int ttl;
public final InetAddress multicastInterface;
public final boolean deferToInterface;
public Config(int port, String group, int bufferSize, int ttl,
InetAddress multicastInterface, boolean deferToInterface) {
this.port = port;
this.group = group;
this.bufferSize = bufferSize;
this.ttl = ttl;
this.multicastInterface = multicastInterface;
this.deferToInterface = deferToInterface;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Config config = (Config) o;
if (bufferSize != config.bufferSize) return false;
if (port != config.port) return false;
if (ttl != config.ttl) return false;
if (group != null ? !group.equals(config.group) : config.group != null) return false;
if (multicastInterface != null ? !multicastInterface.equals(config.multicastInterface) : config.multicastInterface != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = port;
result = 31 * result + (group != null ? group.hashCode() : 0);
result = 31 * result + bufferSize;
result = 31 * result + ttl;
result = 31 * result + (multicastInterface != null ? multicastInterface.hashCode() : 0);
return result;
}
}
/**
* Listener that gets called when data is received on the multicast channel.
*/
public static interface Listener {
void onMessage(BytesReference data, SocketAddress address);
}
/**
* Simple listener that wraps multiple listeners into one.
*/
public static class MultiListener implements Listener {
private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>();
public void add(Listener listener) {
this.listeners.add(listener);
}
public boolean remove(Listener listener) {
return this.listeners.remove(listener);
}
@Override
public void onMessage(BytesReference data, SocketAddress address) {
for (Listener listener : listeners) {
listener.onMessage(data, address);
}
}
}
protected final Listener listener;
private AtomicBoolean closed = new AtomicBoolean();
protected MulticastChannel(Listener listener) {
this.listener = listener;
}
/**
* Send the data over the multicast channel.
*/
public abstract void send(BytesReference data) throws Exception;
/**
* Close the channel.
*/
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
close(listener);
}
}
protected abstract void close(Listener listener);
public static final String SHARED_CHANNEL_NAME = "#shared#";
/**
* A shared channel that keeps a static map of Config -> Shared channels, and closes shared
* channel once their reference count has reached 0. It also handles de-registering relevant
* listener from the shared list of listeners.
*/
private final static class Shared extends MulticastChannel {
private static final Map sharedChannels = Maps.newHashMap();
private static final Object mutex = new Object(); // global mutex so we don't sync on static methods (.class)
static MulticastChannel getSharedChannel(Listener listener, Config config) throws Exception {
synchronized (mutex) {
Shared shared = sharedChannels.get(config);
if (shared != null) {
shared.incRef();
((MultiListener) shared.listener).add(listener);
} else {
MultiListener multiListener = new MultiListener();
multiListener.add(listener);
shared = new Shared(multiListener, new Plain(multiListener, SHARED_CHANNEL_NAME, config));
sharedChannels.put(config, shared);
}
return new Delegate(listener, shared);
}
}
static void close(Shared shared, Listener listener) {
synchronized (mutex) {
// remove this
boolean removed = ((MultiListener) shared.listener).remove(listener);
assert removed : "a listener should be removed";
if (shared.decRef() == 0) {
assert ((MultiListener) shared.listener).listeners.isEmpty();
sharedChannels.remove(shared.channel.getConfig());
shared.channel.close();
}
}
}
final Plain channel;
private int refCount = 1;
Shared(MultiListener listener, Plain channel) {
super(listener);
this.channel = channel;
}
private void incRef() {
refCount++;
}
private int decRef() {
--refCount;
assert refCount >= 0 : "illegal ref counting, close called multiple times";
return refCount;
}
@Override
public void send(BytesReference data) throws Exception {
channel.send(data);
}
@Override
public void close() {
assert false : "Shared references should never be closed directly, only via Delegate";
}
@Override
protected void close(Listener listener) {
close(this, listener);
}
}
/**
* A light weight delegate that wraps another channel, mainly to support delegating
* the close method with the provided listener and not holding existing listener.
*/
private final static class Delegate extends MulticastChannel {
private final MulticastChannel channel;
Delegate(Listener listener, MulticastChannel channel) {
super(listener);
this.channel = channel;
}
@Override
public void send(BytesReference data) throws Exception {
channel.send(data);
}
@Override
protected void close(Listener listener) {
channel.close(listener); // we delegate here to the close with our listener, not with the delegate listener
}
}
/**
* Simple implementation of a channel.
*/
@SuppressForbidden(reason = "I bind to wildcard addresses. I am a total nightmare")
private static class Plain extends MulticastChannel {
private final ESLogger logger;
private final Config config;
private volatile MulticastSocket multicastSocket;
private final DatagramPacket datagramPacketSend;
private final DatagramPacket datagramPacketReceive;
private final Object sendMutex = new Object();
private final Object receiveMutex = new Object();
private final Receiver receiver;
private final Thread receiverThread;
Plain(Listener listener, String name, Config config) throws Exception {
super(listener);
this.logger = ESLoggerFactory.getLogger(name);
this.config = config;
this.datagramPacketReceive = new DatagramPacket(new byte[config.bufferSize], config.bufferSize);
this.datagramPacketSend = new DatagramPacket(new byte[config.bufferSize], config.bufferSize, InetAddress.getByName(config.group), config.port);
this.multicastSocket = buildMulticastSocket(config);
this.receiver = new Receiver();
this.receiverThread = daemonThreadFactory(Settings.builder().put("name", name).build(), "discovery#multicast#receiver").newThread(receiver);
this.receiverThread.start();
}
private MulticastSocket buildMulticastSocket(Config config) throws Exception {
SocketAddress addr = new InetSocketAddress(InetAddress.getByName(config.group), config.port);
MulticastSocket multicastSocket = new MulticastSocket(config.port);
try {
multicastSocket.setTimeToLive(config.ttl);
// OSX is not smart enough to tell that a socket bound to the
// 'lo0' interface needs to make sure to send the UDP packet
// out of the lo0 interface, so we need to do some special
// workarounds to fix it.
if (config.deferToInterface) {
// 'null' here tells the socket to deter to the interface set
// with .setInterface
multicastSocket.joinGroup(addr, null);
multicastSocket.setInterface(config.multicastInterface);
} else {
multicastSocket.setInterface(config.multicastInterface);
multicastSocket.joinGroup(InetAddress.getByName(config.group));
}
multicastSocket.setReceiveBufferSize(config.bufferSize);
multicastSocket.setSendBufferSize(config.bufferSize);
multicastSocket.setSoTimeout(60000);
} catch (Throwable e) {
IOUtils.closeWhileHandlingException(multicastSocket);
throw e;
}
return multicastSocket;
}
public Config getConfig() {
return this.config;
}
@Override
public void send(BytesReference data) throws Exception {
synchronized (sendMutex) {
datagramPacketSend.setData(data.toBytes());
multicastSocket.send(datagramPacketSend);
}
}
@Override
protected void close(Listener listener) {
receiver.stop();
receiverThread.interrupt();
if (multicastSocket != null) {
IOUtils.closeWhileHandlingException(multicastSocket);
multicastSocket = null;
}
try {
receiverThread.join(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private class Receiver implements Runnable {
private volatile boolean running = true;
public void stop() {
running = false;
}
@Override
public void run() {
while (running) {
try {
synchronized (receiveMutex) {
try {
multicastSocket.receive(datagramPacketReceive);
} catch (SocketTimeoutException ignore) {
continue;
} catch (Exception e) {
if (running) {
if (multicastSocket.isClosed()) {
logger.warn("multicast socket closed while running, restarting...");
multicastSocket = buildMulticastSocket(config);
} else {
logger.warn("failed to receive packet, throttling...", e);
Thread.sleep(500);
}
}
continue;
}
}
if (datagramPacketReceive.getData().length > 0) {
listener.onMessage(new BytesArray(datagramPacketReceive.getData()), datagramPacketReceive.getSocketAddress());
}
} catch (Throwable e) {
if (running) {
logger.warn("unexpected exception in multicast receiver", e);
}
}
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy