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

org.jboss.remoting3.util.InvocationTracker Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.jboss.remoting3.util;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Iterator;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.IntFunction;
import java.util.function.IntUnaryOperator;

import org.jboss.remoting3.AbstractDelegatingMessageOutputStream;
import org.jboss.remoting3.Channel;
import org.jboss.remoting3.MessageInputStream;
import org.jboss.remoting3.MessageOutputStream;
import org.jboss.remoting3.RemotingOptions;
import org.jboss.remoting3._private.IntIndexHashMap;
import org.jboss.remoting3._private.IntIndexMap;
import org.wildfly.common.Assert;

/**
 * An invocation tracker.
 *
 * @author David M. Lloyd
 */
public final class InvocationTracker {
    private final IntIndexMap invocations = new IntIndexHashMap(Invocation::getIndex);
    private final MessageTracker messageTracker;
    private final IntUnaryOperator intMasker;

    /**
     * Construct a new instance.
     *
     * @param channel the channel that is being tracked
     * @param maxMessages the maximum number of concurrent messages to allow
     * @param intMasker the function to apply to ID numbers to limit them to a specific range
     */
    public InvocationTracker(final Channel channel, final int maxMessages, final IntUnaryOperator intMasker) {
        this(channel, new MessageTracker(channel, maxMessages), intMasker);
    }

    /**
     * Construct a new instance.
     *
     * @param channel the channel that is being tracked
     * @param messageTracker the message tracker to use
     * @param intMasker the function to apply to ID numbers to limit them to a specific range
     */
    public InvocationTracker(final Channel channel, final MessageTracker messageTracker, final IntUnaryOperator intMasker) {
        Assert.checkNotNullParam("channel", channel);
        Assert.checkNotNullParam("messageTracker", messageTracker);
        Assert.checkNotNullParam("intMasker", intMasker);
        this.messageTracker = messageTracker;
        channel.addCloseHandler((closed, exception) -> connectionClosed(exception));
        this.intMasker = intMasker;
    }

    /**
     * Construct a new instance, using the maximum number of messages from the channel.
     *
     * @param channel the channel that is being tracked
     * @param intMasker the function to apply to ID numbers to limit them to a specific range
     */
    public InvocationTracker(final Channel channel, final IntUnaryOperator intMasker) {
        this(channel, channel.getOption(RemotingOptions.MAX_OUTBOUND_MESSAGES).intValue(), intMasker);
    }

    /**
     * Construct a new instance.
     *
     * @param channel the channel that is being tracked
     * @param maxMessages the maximum number of concurrent messages to allow
     */
    public InvocationTracker(final Channel channel, final int maxMessages) {
        this(channel, maxMessages, InvocationTracker::defaultFunction);
    }

    /**
     * Construct a new instance, using the maximum number of messages from the channel.
     *
     * @param channel the channel that is being tracked
     */
    public InvocationTracker(final Channel channel) {
        this(channel, channel.getOption(RemotingOptions.MAX_OUTBOUND_MESSAGES).intValue(), InvocationTracker::defaultFunction);
    }

    private static int defaultFunction(int random) {
        return random & 0xffff;
    }

    /**
     * Add an invocation to this tracker.
     *
     * @param producer the invocation producer, which may be called more than once
     * @param  the invocation type
     * @return the produced invocation
     */
    public  T addInvocation(IntFunction producer) {
        final ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
        final IntUnaryOperator intMasker = this.intMasker;
        final IntIndexMap invocations = this.invocations;
        int id;
        T invocation;
        for (;;) {
            id = intMasker.applyAsInt(threadLocalRandom.nextInt());
            if (invocations.containsKey(id)) {
                continue;
            }
            invocation = producer.apply(id);
            if (invocations.putIfAbsent(invocation) != null) {
                continue;
            }
            return invocation;
        }
    }

    /**
     * Determine if the tracker contains an entry at the given index.
     *
     * @param index the index
     * @return {@code true} if the tracker contains the entry, {@code false} otherwise
     */
    public boolean containsIndex(int index) {
        return invocations.containsKey(index);
    }

    /**
     * Put an invocation into the tracker if there is none with the corresponding ID.
     *
     * @param invocation the invocation
     * @return the existing invocation, or {@code null} if the put was successful
     */
    public Invocation putIfAbsent(Invocation invocation) {
        return invocations.putIfAbsent(invocation);
    }

    /**
     * Signal the arrival of a response with the given index.
     *
     * @param index the index of the response
     * @param parameter an integer parameter to pass to the response handler (typically a message ID type)
     * @param responseStream the response stream
     * @param remove {@code true} to release the index for subsequent invocations, {@code false} otherwise
     * @return {@code true} if the index was valid, {@code false} otherwise
     */
    public boolean signalResponse(int index, int parameter, MessageInputStream responseStream, boolean remove) {
        final Invocation invocation = remove ? invocations.removeKey(index) : invocations.get(index);
        if (invocation == null) {
            return false;
        }
        invocation.handleResponse(parameter, responseStream);
        return true;
    }

    /**
     * Unconditionally remove an invocation from the map.  This should only be done if the outbound request definitely
     * failed to be written.
     *
     * @param invocation the invocation
     */
    public void remove(final Invocation invocation) {
        invocations.remove(invocation);
    }

    /**
     * Allocate a message, possibly blocking until one is available.
     *
     * @return the allocated message
     * @throws IOException if an error occurs
     */
    public MessageOutputStream allocateMessage() throws IOException {
        try {
            return messageTracker.openMessage();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new InterruptedIOException("Message allocation interrupted");
        }
    }

    /**
     * Allocate a message on behalf of an invocation, possibly blocking until a message is available.  The invocation
     * will automatically be removed if writing the message fails or is cancelled.
     *
     * @param invocation the invocation of the message
     * @return the allocated message
     * @throws IOException if an error occurs
     */
    public MessageOutputStream allocateMessage(Invocation invocation) throws IOException {
        return new AbstractDelegatingMessageOutputStream(allocateMessage()) {
            public MessageOutputStream cancel() {
                super.cancel();
                remove(invocation);
                return this;
            }
        };
    }

    private void connectionClosed(final IOException exception) {
        final Iterator iterator = invocations.iterator();
        while (iterator.hasNext()) {
            final Invocation invocation = iterator.next();
            try {
                if (exception != null) {
                    invocation.handleException(exception);
                } else {
                    invocation.handleClosed();
                }
            } catch (Throwable ignored) {
            }
            iterator.remove();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy