| // |
| // ======================================================================== |
| // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.client; |
| |
| import java.io.IOException; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.locks.ReentrantLock; |
| import java.util.stream.Collectors; |
| |
| import org.eclipse.jetty.client.api.Connection; |
| import org.eclipse.jetty.util.Callback; |
| import org.eclipse.jetty.util.component.ContainerLifeCycle; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| |
| public class MultiplexConnectionPool extends AbstractConnectionPool |
| { |
| private static final Logger LOG = Log.getLogger(MultiplexConnectionPool.class); |
| |
| private final ReentrantLock lock = new ReentrantLock(); |
| private final Deque<Holder> idleConnections; |
| private final Map<Connection, Holder> muxedConnections; |
| private final Map<Connection, Holder> busyConnections; |
| private int maxMultiplex; |
| |
| public MultiplexConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplex) |
| { |
| super(destination, maxConnections, requester); |
| this.idleConnections = new ArrayDeque<>(maxConnections); |
| this.muxedConnections = new HashMap<>(maxConnections); |
| this.busyConnections = new HashMap<>(maxConnections); |
| this.maxMultiplex = maxMultiplex; |
| } |
| |
| protected void lock() |
| { |
| lock.lock(); |
| } |
| |
| protected void unlock() |
| { |
| lock.unlock(); |
| } |
| |
| public int getMaxMultiplex() |
| { |
| lock(); |
| try |
| { |
| return maxMultiplex; |
| } |
| finally |
| { |
| unlock(); |
| } |
| } |
| |
| public void setMaxMultiplex(int maxMultiplex) |
| { |
| lock(); |
| try |
| { |
| this.maxMultiplex = maxMultiplex; |
| } |
| finally |
| { |
| unlock(); |
| } |
| } |
| |
| @Override |
| public boolean isActive(Connection connection) |
| { |
| lock(); |
| try |
| { |
| if (muxedConnections.containsKey(connection)) |
| return true; |
| if (busyConnections.containsKey(connection)) |
| return true; |
| return false; |
| } |
| finally |
| { |
| unlock(); |
| } |
| } |
| |
| @Override |
| protected void onCreated(Connection connection) |
| { |
| lock(); |
| try |
| { |
| // Use "cold" connections as last. |
| idleConnections.offer(new Holder(connection)); |
| } |
| finally |
| { |
| unlock(); |
| } |
| |
| idle(connection, false); |
| } |
| |
| @Override |
| protected Connection activate() |
| { |
| Holder holder; |
| lock(); |
| try |
| { |
| while (true) |
| { |
| if (muxedConnections.isEmpty()) |
| { |
| holder = idleConnections.poll(); |
| if (holder == null) |
| return null; |
| muxedConnections.put(holder.connection, holder); |
| } |
| else |
| { |
| holder = muxedConnections.values().iterator().next(); |
| } |
| |
| if (holder.count < maxMultiplex) |
| { |
| ++holder.count; |
| break; |
| } |
| else |
| { |
| muxedConnections.remove(holder.connection); |
| busyConnections.put(holder.connection, holder); |
| } |
| } |
| } |
| finally |
| { |
| unlock(); |
| } |
| |
| return active(holder.connection); |
| } |
| |
| @Override |
| public boolean release(Connection connection) |
| { |
| boolean closed = isClosed(); |
| boolean idle = false; |
| Holder holder; |
| lock(); |
| try |
| { |
| holder = muxedConnections.get(connection); |
| if (holder != null) |
| { |
| int count = --holder.count; |
| if (count == 0) |
| { |
| muxedConnections.remove(connection); |
| if (!closed) |
| { |
| idleConnections.offerFirst(holder); |
| idle = true; |
| } |
| } |
| } |
| else |
| { |
| holder = busyConnections.remove(connection); |
| if (holder != null) |
| { |
| int count = --holder.count; |
| if (!closed) |
| { |
| if (count == 0) |
| { |
| idleConnections.offerFirst(holder); |
| idle = true; |
| } |
| else |
| { |
| muxedConnections.put(connection, holder); |
| } |
| } |
| } |
| } |
| } |
| finally |
| { |
| unlock(); |
| } |
| |
| if (holder == null) |
| return false; |
| |
| released(connection); |
| if (idle || closed) |
| return idle(connection, closed); |
| return true; |
| } |
| |
| @Override |
| public boolean remove(Connection connection) |
| { |
| return remove(connection, false); |
| } |
| |
| protected boolean remove(Connection connection, boolean force) |
| { |
| boolean activeRemoved = true; |
| boolean idleRemoved = false; |
| lock(); |
| try |
| { |
| Holder holder = muxedConnections.remove(connection); |
| if (holder == null) |
| holder = busyConnections.remove(connection); |
| if (holder == null) |
| { |
| activeRemoved = false; |
| for (Iterator<Holder> iterator = idleConnections.iterator(); iterator.hasNext();) |
| { |
| holder = iterator.next(); |
| if (holder.connection == connection) |
| { |
| idleRemoved = true; |
| iterator.remove(); |
| break; |
| } |
| } |
| } |
| } |
| finally |
| { |
| unlock(); |
| } |
| |
| if (activeRemoved || force) |
| released(connection); |
| boolean removed = activeRemoved || idleRemoved || force; |
| if (removed) |
| removed(connection); |
| return removed; |
| } |
| |
| @Override |
| public void close() |
| { |
| super.close(); |
| |
| List<Connection> connections; |
| lock(); |
| try |
| { |
| connections = idleConnections.stream().map(holder -> holder.connection).collect(Collectors.toList()); |
| connections.addAll(muxedConnections.keySet()); |
| connections.addAll(busyConnections.keySet()); |
| } |
| finally |
| { |
| unlock(); |
| } |
| |
| close(connections); |
| } |
| |
| @Override |
| public void dump(Appendable out, String indent) throws IOException |
| { |
| List<Holder> connections = new ArrayList<>(); |
| lock(); |
| try |
| { |
| connections.addAll(busyConnections.values()); |
| connections.addAll(muxedConnections.values()); |
| connections.addAll(idleConnections); |
| } |
| finally |
| { |
| unlock(); |
| } |
| |
| ContainerLifeCycle.dumpObject(out, this); |
| ContainerLifeCycle.dump(out, indent, connections); |
| } |
| |
| private static class Holder |
| { |
| private final Connection connection; |
| private int count; |
| |
| private Holder(Connection connection) |
| { |
| this.connection = connection; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return String.format("%s[%d]", connection, count); |
| } |
| } |
| } |