class PerHostConnectionSemaphore implements ConnectionSemaphore {

    protected final ConcurrentHashMap<Object, Semaphore> freeChannelsPerHost = new ConcurrentHashMap<>();
    protected final int maxConnectionsPerHost;
    protected final IOException tooManyConnectionsPerHost;
    protected final int acquireTimeout;

    PerHostConnectionSemaphore(int maxConnectionsPerHost, int acquireTimeout) {
        tooManyConnectionsPerHost = unknownStackTrace(new TooManyConnectionsPerHostException(maxConnectionsPerHost),
                PerHostConnectionSemaphore.class, "acquireChannelLock");
        this.maxConnectionsPerHost = maxConnectionsPerHost;
        this.acquireTimeout = Math.max(0, acquireTimeout);
    }

    @Override
    public void acquireChannelLock(Object partitionKey) throws IOException {<FILL_FUNCTION_BODY>}

    @Override
    public void releaseChannelLock(Object partitionKey) {
        getFreeConnectionsForHost(partitionKey).release();
    }

    protected Semaphore getFreeConnectionsForHost(Object partitionKey) {
        return maxConnectionsPerHost > 0 ?
                freeChannelsPerHost.computeIfAbsent(partitionKey, pk -> new Semaphore(maxConnectionsPerHost)) :
                InfiniteSemaphore.INSTANCE;
    }
}

        try {
            if (!getFreeConnectionsForHost(partitionKey).tryAcquire(acquireTimeout, TimeUnit.MILLISECONDS)) {
                throw tooManyConnectionsPerHost;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    
<no_super_class>