All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.pulsar.shade.io.netty.bootstrap.AbstractBootstrap Maven / Gradle / Ivy

There is a newer version: 4.0.0.4
Show newest version
/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project 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:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package org.apache.pulsar.shade.io.netty.bootstrap;

import org.apache.pulsar.shade.io.netty.channel.Channel;
import org.apache.pulsar.shade.io.netty.channel.ChannelFuture;
import org.apache.pulsar.shade.io.netty.channel.ChannelFutureListener;
import org.apache.pulsar.shade.io.netty.channel.ChannelHandler;
import org.apache.pulsar.shade.io.netty.channel.ChannelOption;
import org.apache.pulsar.shade.io.netty.channel.ChannelPromise;
import org.apache.pulsar.shade.io.netty.channel.DefaultChannelPromise;
import org.apache.pulsar.shade.io.netty.channel.EventLoop;
import org.apache.pulsar.shade.io.netty.channel.EventLoopGroup;
import org.apache.pulsar.shade.io.netty.channel.ReflectiveChannelFactory;
import org.apache.pulsar.shade.io.netty.util.AttributeKey;
import org.apache.pulsar.shade.io.netty.util.concurrent.EventExecutor;
import org.apache.pulsar.shade.io.netty.util.concurrent.GlobalEventExecutor;
import org.apache.pulsar.shade.io.netty.util.internal.ObjectUtil;
import org.apache.pulsar.shade.io.netty.util.internal.SocketUtils;
import org.apache.pulsar.shade.io.netty.util.internal.StringUtil;
import org.apache.pulsar.shade.io.netty.util.internal.logging.InternalLogger;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * {@link AbstractBootstrap} is a helper class that makes it easy to bootstrap a {@link Channel}. It support
 * method-chaining to provide an easy way to configure the {@link AbstractBootstrap}.
 *
 * 

When not used in a {@link ServerBootstrap} context, the {@link #bind()} methods are useful for connectionless * transports such as datagram (UDP).

*/ public abstract class AbstractBootstrap, C extends Channel> implements Cloneable { @SuppressWarnings("unchecked") private static final Map.Entry, Object>[] EMPTY_OPTION_ARRAY = new Map.Entry[0]; @SuppressWarnings("unchecked") private static final Map.Entry, Object>[] EMPTY_ATTRIBUTE_ARRAY = new Map.Entry[0]; volatile EventLoopGroup group; @SuppressWarnings("deprecation") private volatile ChannelFactory channelFactory; private volatile SocketAddress localAddress; // The order in which ChannelOptions are applied is important they may depend on each other for validation // purposes. private final Map, Object> options = new LinkedHashMap, Object>(); private final Map, Object> attrs = new ConcurrentHashMap, Object>(); private volatile ChannelHandler handler; private volatile ClassLoader extensionsClassLoader; AbstractBootstrap() { // Disallow extending from a different package. } AbstractBootstrap(AbstractBootstrap bootstrap) { group = bootstrap.group; channelFactory = bootstrap.channelFactory; handler = bootstrap.handler; localAddress = bootstrap.localAddress; synchronized (bootstrap.options) { options.putAll(bootstrap.options); } attrs.putAll(bootstrap.attrs); extensionsClassLoader = bootstrap.extensionsClassLoader; } /** * The {@link EventLoopGroup} which is used to handle all the events for the to-be-created * {@link Channel} */ public B group(EventLoopGroup group) { ObjectUtil.checkNotNull(group, "group"); if (this.group != null) { throw new IllegalStateException("group set already"); } this.group = group; return self(); } @SuppressWarnings("unchecked") private B self() { return (B) this; } /** * The {@link Class} which is used to create {@link Channel} instances from. * You either use this or {@link #channelFactory(org.apache.pulsar.shade.io.netty.channel.ChannelFactory)} if your * {@link Channel} implementation has no no-args constructor. */ public B channel(Class channelClass) { return channelFactory(new ReflectiveChannelFactory( ObjectUtil.checkNotNull(channelClass, "channelClass") )); } /** * @deprecated Use {@link #channelFactory(org.apache.pulsar.shade.io.netty.channel.ChannelFactory)} instead. */ @Deprecated public B channelFactory(ChannelFactory channelFactory) { ObjectUtil.checkNotNull(channelFactory, "channelFactory"); if (this.channelFactory != null) { throw new IllegalStateException("channelFactory set already"); } this.channelFactory = channelFactory; return self(); } /** * {@link org.apache.pulsar.shade.io.netty.channel.ChannelFactory} which is used to create {@link Channel} instances from * when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)} * is not working for you because of some more complex needs. If your {@link Channel} implementation * has a no-args constructor, its highly recommend to just use {@link #channel(Class)} to * simplify your code. */ @SuppressWarnings({ "unchecked", "deprecation" }) public B channelFactory(org.apache.pulsar.shade.io.netty.channel.ChannelFactory channelFactory) { return channelFactory((ChannelFactory) channelFactory); } /** * The {@link SocketAddress} which is used to bind the local "end" to. */ public B localAddress(SocketAddress localAddress) { this.localAddress = localAddress; return self(); } /** * @see #localAddress(SocketAddress) */ public B localAddress(int inetPort) { return localAddress(new InetSocketAddress(inetPort)); } /** * @see #localAddress(SocketAddress) */ public B localAddress(String inetHost, int inetPort) { return localAddress(SocketUtils.socketAddress(inetHost, inetPort)); } /** * @see #localAddress(SocketAddress) */ public B localAddress(InetAddress inetHost, int inetPort) { return localAddress(new InetSocketAddress(inetHost, inetPort)); } /** * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they got * created. Use a value of {@code null} to remove a previous set {@link ChannelOption}. */ public B option(ChannelOption option, T value) { ObjectUtil.checkNotNull(option, "option"); synchronized (options) { if (value == null) { options.remove(option); } else { options.put(option, value); } } return self(); } /** * Allow to specify an initial attribute of the newly created {@link Channel}. If the {@code value} is * {@code null}, the attribute of the specified {@code key} is removed. */ public B attr(AttributeKey key, T value) { ObjectUtil.checkNotNull(key, "key"); if (value == null) { attrs.remove(key); } else { attrs.put(key, value); } return self(); } /** * Load {@link ChannelInitializerExtension}s using the given class loader. *

* By default, the extensions will be loaded by the same class loader that loaded this bootstrap class. * * @param classLoader The class loader to use for loading {@link ChannelInitializerExtension}s. * @return This bootstrap. */ public B extensionsClassLoader(ClassLoader classLoader) { extensionsClassLoader = classLoader; return self(); } /** * Validate all the parameters. Sub-classes may override this, but should * call the super method in that case. */ public B validate() { if (group == null) { throw new IllegalStateException("group not set"); } if (channelFactory == null) { throw new IllegalStateException("channel or channelFactory not set"); } return self(); } /** * Returns a deep clone of this bootstrap which has the identical configuration. This method is useful when making * multiple {@link Channel}s with similar settings. Please note that this method does not clone the * {@link EventLoopGroup} deeply but shallowly, making the group a shared resource. */ @Override @SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException") public abstract B clone(); /** * Create a new {@link Channel} and register it with an {@link EventLoop}. */ public ChannelFuture register() { validate(); return initAndRegister(); } /** * Create a new {@link Channel} and bind it. */ public ChannelFuture bind() { validate(); SocketAddress localAddress = this.localAddress; if (localAddress == null) { throw new IllegalStateException("localAddress not set"); } return doBind(localAddress); } /** * Create a new {@link Channel} and bind it. */ public ChannelFuture bind(int inetPort) { return bind(new InetSocketAddress(inetPort)); } /** * Create a new {@link Channel} and bind it. */ public ChannelFuture bind(String inetHost, int inetPort) { return bind(SocketUtils.socketAddress(inetHost, inetPort)); } /** * Create a new {@link Channel} and bind it. */ public ChannelFuture bind(InetAddress inetHost, int inetPort) { return bind(new InetSocketAddress(inetHost, inetPort)); } /** * Create a new {@link Channel} and bind it. */ public ChannelFuture bind(SocketAddress localAddress) { validate(); return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress")); } private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); if (regFuture.cause() != null) { return regFuture; } if (regFuture.isDone()) { // At this point we know that the registration was complete and successful. ChannelPromise promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); return promise; } else { // Registration future is almost always fulfilled already, but just in case it's not. final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); regFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { Throwable cause = future.cause(); if (cause != null) { // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an // IllegalStateException once we try to access the EventLoop of the Channel. promise.setFailure(cause); } else { // Registration was successful, so set the correct executor to use. // See https://github.com/netty/netty/issues/2586 promise.registered(); doBind0(regFuture, channel, localAddress, promise); } } }); return promise; } } final ChannelFuture initAndRegister() { Channel channel = null; try { channel = channelFactory.newChannel(); init(channel); } catch (Throwable t) { if (channel != null) { // channel can be null if newChannel crashed (eg SocketException("too many open files")) channel.unsafe().closeForcibly(); // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); } // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t); } ChannelFuture regFuture = config().group().register(channel); if (regFuture.cause() != null) { if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } } // If we are here and the promise is not failed, it's one of the following cases: // 1) If we attempted registration from the event loop, the registration has been completed at this point. // i.e. It's safe to attempt bind() or connect() now because the channel has been registered. // 2) If we attempted registration from the other thread, the registration request has been successfully // added to the event loop's task queue for later execution. // i.e. It's safe to attempt bind() or connect() now: // because bind() or connect() will be executed *after* the scheduled registration task is executed // because register(), bind(), and connect() are all bound to the same thread. return regFuture; } abstract void init(Channel channel) throws Exception; Collection getInitializerExtensions() { ClassLoader loader = extensionsClassLoader; if (loader == null) { loader = getClass().getClassLoader(); } return ChannelInitializerExtensions.getExtensions().extensions(loader); } private static void doBind0( final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // the pipeline in its channelRegistered() implementation. channel.eventLoop().execute(new Runnable() { @Override public void run() { if (regFuture.isSuccess()) { channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { promise.setFailure(regFuture.cause()); } } }); } /** * the {@link ChannelHandler} to use for serving the requests. */ public B handler(ChannelHandler handler) { this.handler = ObjectUtil.checkNotNull(handler, "handler"); return self(); } /** * Returns the configured {@link EventLoopGroup} or {@code null} if non is configured yet. * * @deprecated Use {@link #config()} instead. */ @Deprecated public final EventLoopGroup group() { return group; } /** * Returns the {@link AbstractBootstrapConfig} object that can be used to obtain the current config * of the bootstrap. */ public abstract AbstractBootstrapConfig config(); final Map.Entry, Object>[] newOptionsArray() { return newOptionsArray(options); } static Map.Entry, Object>[] newOptionsArray(Map, Object> options) { synchronized (options) { return new LinkedHashMap, Object>(options).entrySet().toArray(EMPTY_OPTION_ARRAY); } } final Map.Entry, Object>[] newAttributesArray() { return newAttributesArray(attrs0()); } static Map.Entry, Object>[] newAttributesArray(Map, Object> attributes) { return attributes.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY); } final Map, Object> options0() { return options; } final Map, Object> attrs0() { return attrs; } final SocketAddress localAddress() { return localAddress; } @SuppressWarnings("deprecation") final ChannelFactory channelFactory() { return channelFactory; } final ChannelHandler handler() { return handler; } final Map, Object> options() { synchronized (options) { return copiedMap(options); } } final Map, Object> attrs() { return copiedMap(attrs); } static Map copiedMap(Map map) { if (map.isEmpty()) { return Collections.emptyMap(); } return Collections.unmodifiableMap(new HashMap(map)); } static void setAttributes(Channel channel, Map.Entry, Object>[] attrs) { for (Map.Entry, Object> e: attrs) { @SuppressWarnings("unchecked") AttributeKey key = (AttributeKey) e.getKey(); channel.attr(key).set(e.getValue()); } } static void setChannelOptions( Channel channel, Map.Entry, Object>[] options, InternalLogger logger) { for (Map.Entry, Object> e: options) { setChannelOption(channel, e.getKey(), e.getValue(), logger); } } @SuppressWarnings("unchecked") private static void setChannelOption( Channel channel, ChannelOption option, Object value, InternalLogger logger) { try { if (!channel.config().setOption((ChannelOption) option, value)) { logger.warn("Unknown channel option '{}' for channel '{}'", option, channel); } } catch (Throwable t) { logger.warn( "Failed to set channel option '{}' with value '{}' for channel '{}'", option, value, channel, t); } } @Override public String toString() { StringBuilder buf = new StringBuilder() .append(StringUtil.simpleClassName(this)) .append('(').append(config()).append(')'); return buf.toString(); } static final class PendingRegistrationPromise extends DefaultChannelPromise { // Is set to the correct EventExecutor once the registration was successful. Otherwise it will // stay null and so the GlobalEventExecutor.INSTANCE will be used for notifications. private volatile boolean registered; PendingRegistrationPromise(Channel channel) { super(channel); } void registered() { registered = true; } @Override protected EventExecutor executor() { if (registered) { // If the registration was a success executor is set. // // See https://github.com/netty/netty/issues/2586 return super.executor(); } // The registration failed so we can only use the GlobalEventExecutor as last resort to notify. return GlobalEventExecutor.INSTANCE; } } }