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

com.sun.xml.ws.transport.tcp.connectioncache.impl.transport.OutboundConnectionCacheBlockingImpl Maven / Gradle / Ivy

There is a newer version: 4.0.3
Show newest version
/*
 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package com.sun.xml.ws.transport.tcp.connectioncache.impl.transport;

import com.sun.xml.ws.transport.tcp.connectioncache.spi.concurrent.ConcurrentQueue;
import com.sun.xml.ws.transport.tcp.connectioncache.spi.transport.Connection;
import com.sun.xml.ws.transport.tcp.connectioncache.spi.transport.ConnectionFinder;
import com.sun.xml.ws.transport.tcp.connectioncache.spi.transport.ContactInfo;
import com.sun.xml.ws.transport.tcp.connectioncache.spi.transport.OutboundConnectionCache;
import java.io.IOException ;

import java.util.Map ;
import java.util.HashMap ;
import java.util.Queue ;
import java.util.Collection ;
import java.util.Collections ;

import java.util.concurrent.LinkedBlockingQueue ;

import java.util.logging.Logger ;

public final class OutboundConnectionCacheBlockingImpl
        extends ConnectionCacheBlockingBase
        implements OutboundConnectionCache {
    
    // Configuration data
    // XXX we may want this data to be dynamically re-configurable
    private final int maxParallelConnections ;	// Maximum number of
    // connections we will open
    // to the same endpoint
    
    private Map,CacheEntry> entryMap ;
    private Map> connectionMap ;
    
    public int maxParallelConnections() {
        return maxParallelConnections ;
    }
    
    protected String thisClassName() {
        return "OutboundConnectionCacheBlockingImpl" ;
    }
    
    // NEW: connection was just created; currently not queued
    // BUSY: connection queued on busyConnections queue
    // IDLE: connection queued on idleConnections queue
    private enum ConnectionStateValue { NEW, BUSY, IDLE }
    
    private static final class ConnectionState {
        ConnectionStateValue csv ;			// Indicates state of
        // connection
        final ContactInfo cinfo ;			// ContactInfo used to
        // create this
        // Connection
        final C connection ;				// Connection of the
        // ConnectionState
        final CacheEntry entry ;			// This Connection's
        // CacheEntry
        
        int busyCount ;					// Number of calls to
        // get without release
        int expectedResponseCount ;			// Number of expected
        // responses not yet
        // received
        
        // At all times, a connection is either on the busy or idle queue in
        // its ConnectionEntry.  If the connection is on the idle queue,
        // reclaimableHandle may also be non-null if the Connection is also on
        // the reclaimableConnections queue.
        ConcurrentQueue.Handle reclaimableHandle ;	// non-null iff
        // connection is not
        // in use and has no
        // outstanding requests
        
        ConnectionState( final ContactInfo cinfo, final CacheEntry entry,
                final C conn ) {
            
            this.csv = ConnectionStateValue.NEW ;
            this.cinfo = cinfo ;
            this.connection = conn ;
            this.entry = entry ;
            
            busyCount = 0 ;
            expectedResponseCount = 0 ;
            reclaimableHandle = null ;
        }
        
        public String toString() {
            return "ConnectionState["
                    + "cinfo=" + cinfo
                    + " connection=" + connection
                    + " busyCount=" + busyCount
                    + " expectedResponseCount=" + expectedResponseCount
                    + "]" ;
        }
    }
    
    // Represents an entry in the outbound connection cache.
    // This version handles normal shareable ContactInfo
    // (we also need to handle no share).
    private static final class CacheEntry {
        final Queue idleConnections = new LinkedBlockingQueue() ;
        final Collection idleConnectionsView =
                Collections.unmodifiableCollection( idleConnections ) ;
        
        final Queue busyConnections = new LinkedBlockingQueue() ;
        final Collection busyConnectionsView =
                Collections.unmodifiableCollection( busyConnections ) ;
        
        public int totalConnections() {
            return idleConnections.size() + busyConnections.size() ;
        }
    }
    
    public OutboundConnectionCacheBlockingImpl( final String cacheType,
            final int highWaterMark, final int numberToReclaim,
            final int maxParallelConnections, Logger logger ) {
        
        super( cacheType, highWaterMark, numberToReclaim, logger ) ;
        
        if (maxParallelConnections < 1)
            throw new IllegalArgumentException(
                    "maxParallelConnections must be > 0" ) ;
        
        this.maxParallelConnections = maxParallelConnections ;
        
        this.entryMap = new HashMap,CacheEntry>() ;
        this.connectionMap = new HashMap>() ;
        
        if (debug()) {
            dprint(".constructor completed: " + cacheType );
        }
    }
    
    public boolean canCreateNewConnection( ContactInfo cinfo ) {
        CacheEntry entry = entryMap.get( cinfo ) ;
        if (entry == null)
            return true ;
        
        return internalCanCreateNewConnection( entry ) ;
    }
    
    private boolean internalCanCreateNewConnection( final CacheEntry entry ) {
        final int totalConnectionsInEntry = entry.totalConnections() ;
        
        final boolean createNewConnection =
                (totalConnectionsInEntry == 0) ||
                ((numberOfConnections() < highWaterMark()) &&
                (totalConnectionsInEntry < maxParallelConnections)) ;
        
        return createNewConnection ;
    }
    
    private CacheEntry getEntry( final ContactInfo cinfo
            ) throws IOException {
        
        if (debug()) {
            dprint( "->getEntry: " + cinfo ) ;
        }
        
        try {
            // This should be the only place a CacheEntry is constructed.
            CacheEntry result = entryMap.get( cinfo ) ;
            if (result == null) {
                if (debug()) {
                    dprint( ".getEntry: " + cinfo
                            + " creating new CacheEntry" ) ;
                }
                
                result = new CacheEntry() ;
                entryMap.put( cinfo, result ) ;
            } else {
                if (debug()) {
                    dprint( ".getEntry: " + cinfo +
                            " re-using existing CacheEntry" ) ;
                }
            }
            
            return result ;
        } finally {
            if (debug()) {
                dprint( "<-getEntry: " + cinfo ) ;
            }
        }
    }
    
    // Note that tryNewConnection will ALWAYS create a new connection if
    // no connection currently exists.
    private C tryNewConnection( final CacheEntry entry,
            final ContactInfo cinfo ) throws IOException {
        
        if (debug())
            dprint( "->tryNewConnection: " + cinfo ) ;
        
        try {
            C conn = null ;
            
            if (internalCanCreateNewConnection(entry)) {
                // If this throws an exception just let it
                // propagate: let a higher layer handle a
                // connection creation failure.
                conn = cinfo.createConnection() ;
                
                if (debug()) {
                    dprint( ".tryNewConnection: " + cinfo
                            + " created connection " + conn ) ;
                }
            }
            
            return conn ;
        } finally {
            if (debug())
                dprint( "<-tryNewConnection: " + cinfo ) ;
        }
    }
    
    private void decrementTotalIdle() {
        if (debug())
            dprint( "->decrementTotalIdle: totalIdle = "
                    + totalIdle ) ;
        
        try {
            if (totalIdle > 0) {
                totalIdle-- ;
            } else {
                if (debug()) {
                    dprint( ".decrementTotalIdle: "
                            + "incorrect idle count: was already 0" ) ;
                }
            }
        } finally {
            if (debug()) {
                dprint( "<-decrementTotalIdle: totalIdle = "
                        + totalIdle ) ;
            }
        }
    }
    
    private void decrementTotalBusy() {
        if (debug())
            dprint( "->decrementTotalBusy: totalBusy = "
                    + totalBusy ) ;
        
        try {
            if (totalBusy > 0) {
                totalBusy-- ;
            } else {
                if (debug()) {
                    dprint( ".decrementTotalBusy: "
                            + "incorrect idle count: was already 0" ) ;
                }
            }
        } finally {
            if (debug()) {
                dprint( "<-decrementTotalBusy: totalBusy = "
                        + totalBusy ) ;
            }
        }
    }
    
    // Update queues and counts to make the result busy.
    private void makeResultBusy( C result, ConnectionState cs,
            CacheEntry entry ) {
        
        if (debug())
            dprint( "->makeResultBusy: " + result
                    + " was previously " + cs.csv ) ;
        
        try {
            switch (cs.csv) {
                case NEW :
                    totalBusy++ ;
                    break ;
                    
                case IDLE :
                    totalBusy++ ;
                    decrementTotalIdle() ;
                    
                    final ConcurrentQueue.Handle handle =
                            cs.reclaimableHandle ;
                    
                    if (handle != null) {
                        if (!handle.remove()) {
                            if (debug()) {
                                dprint( ".makeResultBusy: " + cs.cinfo
                                        + " result was not on reclaimable Q" ) ;
                            }
                        }
                        cs.reclaimableHandle = null ;
                    }
                    break ;
                    
                case BUSY :
                    // Nothing to do here
                    break ;
            }
            
            entry.busyConnections.offer( result ) ;
            cs.csv = ConnectionStateValue.BUSY ;
            cs.busyCount++ ;
        } finally {
            if (debug())
                dprint( "<-makeResultBusy: " + result ) ;
        }
    }
    
    private C tryIdleConnections( CacheEntry entry ) {
        if (debug()) {
            dprint( "->tryIdleConnections" ) ;
        }
        
        try {
            return entry.idleConnections.poll() ;
        } finally {
            if (debug()) {
                dprint( "<-tryIdleConnections" ) ;
            }
        }
    }
    
    private C tryBusyConnections( CacheEntry entry ) {
        // Use a busy connection.  Note that there MUST be a busy
        // connection available at this point, because
        // tryNewConnection did not create a new connection.
        if (debug()) {
            dprint( "->tryBusyConnections" ) ;
        }
        
        try {
            C result = entry.busyConnections.poll() ;
            
            if (result == null) {
                throw new RuntimeException(
                        "INTERNAL ERROR: no busy connection available" ) ;
            }
            
            return result ;
        } finally {
            if (debug()) {
                dprint( "<-tryBusyConnections" ) ;
            }
        }
    }
    
    public synchronized C get( final ContactInfo cinfo
            ) throws IOException {
        
        return get( cinfo, null ) ;
    }
    
    public synchronized ConnectionState getConnectionState(
            ContactInfo cinfo, CacheEntry entry, C conn ) {
        
        if (debug())
            dprint( "->getConnectionState: " + conn ) ;
        
        try {
            ConnectionState cs = connectionMap.get( conn ) ;
            if (cs == null) {
                if (debug())
                    dprint( ".getConnectionState: " + conn
                            + " creating new ConnectionState" + cs ) ;
                
                cs = new ConnectionState( cinfo, entry, conn ) ;
                connectionMap.put( conn, cs ) ;
            } else {
                if (debug())
                    dprint( ".getConnectionState: " + conn
                            + " found ConnectionState" + cs ) ;
            }
            
            return cs ;
        } finally {
            if (debug())
                dprint( "<-getConnectionState: " + conn ) ;
        }
    }
    
    public synchronized C get( final ContactInfo cinfo,
            final ConnectionFinder finder ) throws IOException {
        
        if (debug()) {
            dprint( "->get: " + cinfo ) ;
        }
        
        ConnectionState cs = null ;
        
        try {
            final CacheEntry entry = getEntry( cinfo ) ;
            C result = null ;
            
            if (numberOfConnections() >= highWaterMark()) {
                // This reclaim probably does nothing, because
                // connections are reclaimed on release in the
                // overflow state.
                reclaim() ;
            }
            
            if (finder != null) {
                // Try the finder if present.
                if (debug()) {
                    dprint( ".get: " + cinfo +
                            " Calling the finder to get a connection" ) ;
                }
                
                result = finder.find( cinfo, entry.idleConnectionsView,
                        entry.busyConnectionsView ) ;
                
                if (result != null) {
                    cs = getConnectionState( cinfo, entry, result ) ;
                    
                    // Dequeue from cache entry if not NEW
                    if (cs.csv == ConnectionStateValue.BUSY)
                        entry.busyConnections.remove( result ) ;
                    else if (cs.csv == ConnectionStateValue.IDLE)
                        entry.idleConnections.remove( result ) ;
                }
            }
            
            if (result == null) {
                result = tryIdleConnections( entry ) ;
            }
            
            if (result == null) {
                result = tryNewConnection( entry, cinfo ) ;
            }
            
            if (result == null) {
                result = tryBusyConnections( entry ) ;
            }
            
            if (cs == null)
                cs = getConnectionState( cinfo, entry, result ) ;
            
            makeResultBusy( result, cs, entry ) ;
            return result ;
        } finally {
            if (debug()) {
                dprint( ".get " + cinfo
                        + " totalIdle=" + totalIdle
                        + " totalBusy=" + totalBusy ) ;
                
                dprint( "<-get " + cinfo + " ConnectionState=" + cs ) ;
            }
        }
    }
    
    // If overflow, close conn and return true,
    // otherwise enqueue on reclaimable queue and return false.
    private boolean reclaimOrClose( ConnectionState cs, final C conn ) {
        if (debug())
            dprint( "->reclaimOrClose: " + conn ) ;
        
        try {
            final boolean isOverflow = numberOfConnections() >
                    highWaterMark() ;
            
            if (isOverflow) {
                if (debug()) {
                    dprint( ".reclaimOrClose: closing overflow connection "
                            + conn ) ;
                }
                
                close( conn ) ;
            } else {
                if (debug()) {
                    dprint( ".reclaimOrClose: queuing reclaimable connection "
                            + conn ) ;
                }
                
                cs.reclaimableHandle =
                        reclaimableConnections.offer( conn ) ;
            }
            
            return isOverflow ;
        } finally {
            if (debug())
                dprint( "<-reclaimOrClose: " + conn ) ;
        }
    }
    
    public synchronized void release( final C conn,
            final int numResponsesExpected ) {
        
        if (debug()) {
            dprint( "->release: " + conn
                    + " expecting " + numResponsesExpected + " responses" ) ;
        }
        
        final ConnectionState cs = connectionMap.get( conn ) ;
        
        try {
            if (cs == null) {
                if (debug()) {
                    dprint( ".release: " + conn + " was closed" ) ;
                }
                
                return ;
            } else {
                cs.expectedResponseCount += numResponsesExpected ;
                int numResp = cs.expectedResponseCount ;
                int numBusy = --cs.busyCount ;
                if (numBusy < 0) {
                    if (debug()) {
                        dprint( ".release: " + conn + " numBusy=" +
                                numBusy + " is < 0: error" ) ;
                    }
                    
                    cs.busyCount = 0 ;
                    return ;
                }
                
                if (debug()) {
                    dprint( ".release: " + numResp + " responses expected" ) ;
                    dprint( ".release: " + numBusy + " busy count" ) ;
                }
                
                if (numBusy == 0) {
                    final CacheEntry entry = cs.entry ;
                    boolean wasOnBusy = entry.busyConnections.remove( conn ) ;
                    if (!wasOnBusy)
                        if (debug())
                            dprint( ".release: " + conn
                                    + " was NOT on busy queue, "
                                    + "but should have been" ) ;
                    
                    boolean connectionClosed = false ;
                    if (numResp == 0) {
                        connectionClosed = reclaimOrClose( cs, conn ) ;
                    }
                    
                    decrementTotalBusy() ;
                    
                    if (!connectionClosed) {
                        if (debug()) {
                            dprint( ".release: queuing idle connection "
                                    + conn ) ;
                        }
                        
                        totalIdle++ ;
                        entry.idleConnections.offer( conn ) ;
                        cs.csv = ConnectionStateValue.IDLE ;
                    }
                }
            }
        } finally {
            if (debug()) {
                dprint( ".release " + conn
                        + " cs=" + cs
                        + " totalIdle=" + totalIdle
                        + " totalBusy=" + totalBusy ) ;
                
                dprint( "<-release" + conn ) ;
            }
        }
    }
    
    /** Decrement the number of expected responses.  When a connection is idle
     * and has no expected responses, it can be reclaimed.
     */
    public synchronized void responseReceived( final C conn ) {
        if (debug()) {
            dprint( "->responseReceived: " + conn ) ;
        }
        
        try {
            final ConnectionState cs = connectionMap.get( conn ) ;
            if (cs == null) {
                if (debug()) {
                    dprint(
                            ".responseReceived: "
                            + "received response on closed connection "
                            + conn ) ;
                }
                
                return ;
            }
            
            final int waitCount = --cs.expectedResponseCount ;
            
            if (debug())  {
                dprint( ".responseReceived: " + conn
                        + " waitCount=" + waitCount ) ;
            }
            
            if (waitCount < 0) {
                if (debug())  {
                    dprint( ".responseReceived: " + conn
                            + " incorrect call: error" ) ;
                }
                cs.expectedResponseCount = 0 ;
                return ;
            }
            
            if ((waitCount == 0) && (cs.busyCount == 0)) {
                reclaimOrClose( cs, conn ) ;
            }
        } finally {
            if (debug()) {
                dprint( "<-responseReceived: " + conn ) ;
            }
        }
    }
    
    /** Close a connection, regardless of whether the connection is busy
     * or not.
     */
    public synchronized void close( final C conn ) {
        if (debug()) {
            dprint( "->close: " + conn ) ;
        }
        
        try {
            final ConnectionState cs = connectionMap.remove( conn ) ;
            if (cs == null) {
                if (debug()) {
                    dprint( ".close: " + conn + " was already closed" ) ;
                }
                
                return ;
            }
            
            if (debug()) {
                dprint( ".close: " + conn
                        + "Connection state=" + cs ) ;
            }
            
            final ConcurrentQueue.Handle rh = cs.reclaimableHandle ;
            if (rh != null) {
                boolean result = rh.remove() ;
                if (debug()) {
                    dprint( ".close: " + conn
                            + "reclaimableHandle .remove = " + result ) ;
                }
            }
            
            if (cs.entry.busyConnections.remove( conn )) {
                if (debug()) {
                    dprint( ".close: " + conn
                            + " removed from busyConnections" ) ;
                }
                
                decrementTotalBusy() ;
            }
            
            if (cs.entry.idleConnections.remove( conn )) {
                if (debug()) {
                    dprint( ".close: " + conn
                            + " removed from idleConnections" ) ;
                }
                
                decrementTotalIdle() ;
            }
            
            try {
                conn.close() ;
            } catch (IOException exc) {
                if (debug())
                    dprint( ".close: " + conn + ": Caught IOException on close:"
                            + exc ) ;
            }
        } finally {
            if (debug()) {
                dprintStatistics() ;
                dprint( "<-close: " + conn ) ;
            }
        }
    }
}

// End of file.




© 2015 - 2024 Weber Informatics LLC | Privacy Policy