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

net.openhft.chronicle.wire.channel.impl.WireExchanger Maven / Gradle / Ivy

There is a newer version: 2.27ea1
Show newest version
/*
 * Copyright 2016-2022 chronicle.software
 *
 *       https://chronicle.software
 *
 * 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 net.openhft.chronicle.wire.channel.impl;

import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.UnsafeMemory;
import net.openhft.chronicle.core.io.SimpleCloseable;
import net.openhft.chronicle.wire.*;
import org.jetbrains.annotations.NotNull;

import static net.openhft.chronicle.core.UnsafeMemory.MEMORY;

public class WireExchanger extends SimpleCloseable implements MarshallableOut {

    // State constants representing the status of the wires.
    static final int USED_MASK = 0x001;
    static final int FREE = 0x000, LOCKED = 0x010, DIRTY = 0x100;
    static final int FREE0 = 0x000, LOCKED0 = 0x010, DIRTY0 = 0x100;
    static final int FREE1 = 0x001, LOCKED1 = 0x011, DIRTY1 = 0x101;
    private static final int INIT_CAPACITY = TCPChronicleChannel.CAPACITY;
    private static final long valueOffset;

    // Empty wire representation for initialization purposes.
    private static final Wire EMPTY_WIRE = WireType.BINARY_LIGHT.apply(Bytes.from(""));

    static {
        try {
            valueOffset = UnsafeMemory.unsafeObjectFieldOffset(
                    WireExchanger.class.getDeclaredField("value"));
        } catch (Exception ex) {
            throw new AssertionError(ex);
        }
    }

    // The two wires used for data exchange.
    private final Wire wire0, wire1;

    // The document context associated with write operations.
    private final WEDocumentContext writeContext = new WEDocumentContext();
    private int delay = 0;

    // Volatile value used for atomic operations to ensure thread safety.
    private volatile int value;

    /**
     * Initializes the WireExchanger with a default initial capacity.
     */
    public WireExchanger() {
        this(INIT_CAPACITY);
    }

    /**
     * Initializes the WireExchanger with the specified initial capacity.
     *
     * @param initCapacity The initial capacity for the wires.
     */
    @NotNull
    public WireExchanger(int initCapacity) {
        wire0 = WireType.BINARY_LIGHT.apply(
                Bytes.elasticByteBuffer(initCapacity));
        wire0.bytes().singleThreadedCheckDisabled(true);
        wire1 = WireType.BINARY_LIGHT.apply(
                Bytes.elasticByteBuffer(initCapacity));
        wire1.bytes().singleThreadedCheckDisabled(true);
    }

    @Override
    protected void performClose() {
        super.performClose();
        wire0.bytes().releaseLast();
        wire1.bytes().releaseLast();
    }

    /**
     * Acquires a producer wire for data exchange.
     * This method ensures that the wire is not locked or dirty before returning it.
     * If the wire is currently in use, this method will attempt to release the producer and acquire a new one.
     *
     * @return A wire that can be used for producing data.
     */
    public Wire acquireProducer() {
        {
            int val = lock();
            int writeTo = val & USED_MASK;
            final Wire wire = wireAt(writeTo);
            if (wire.bytes().readRemaining() <= INIT_CAPACITY / 2) {
                if (delay > 1)
                    delay--;
                return wire;
            }
            releaseProducer();
        }
        return acquireProducer2();
    }

    /**
     * Acquires a secondary producer wire for data exchange.
     * This method is invoked when the primary wire is found to be in use.
     * The producer will pause momentarily before attempting to access the wire.
     *
     * @return A wire that can be used for producing data.
     */
    @NotNull
    private Wire acquireProducer2() {
        Jvm.pause(delay++);
        {
            int val2 = lock();
            int writeTo2 = val2 & USED_MASK;
            final Wire wire2 = wireAt(writeTo2);
            final long used = wire2.bytes().readRemaining();
            if (used > INIT_CAPACITY * 4L / 5) {
                double ratio = (double) used / INIT_CAPACITY;
                Jvm.perf().on(getClass(), "Producer buffering " + (int) (100 * ratio) + "%");
            }

            return wire2;
        }
    }

    /**
     * Marks the currently locked producer wire as dirty after data has been written into it.
     * This signifies that the wire contains new data that hasn't been read by the consumer yet.
     */
    public void releaseProducer() {
        final int val2 = DIRTY | (value & USED_MASK);
        MEMORY.writeOrderedInt(this, valueOffset, val2);
    }

    /**
     * Returns the wire instance based on the provided index.
     *
     * @param writeTo The index representing which wire to access.
     * @return The wire associated with the provided index.
     */
    private Wire wireAt(int writeTo) {
        return writeTo == 0 ? wire0 : wire1;
    }

    /**
     * Acquires a consumer wire for data reading.
     * If the wire does not have any new data (i.e., it isn't dirty), an empty wire is returned.
     *
     * @return A wire that can be used for consuming data.
     */
    public Wire acquireConsumer() {
        if ((value & DIRTY) == 0)
            return EMPTY_WIRE;

        int val = lock();
        int writeTo = val & USED_MASK;
        final int val2 = FREE | writeTo ^ USED_MASK;
        // assume one consumer
        MEMORY.writeOrderedInt(this, valueOffset, val2);

        return wireAt(writeTo);
    }

    /**
     * Attempts to lock a wire for exclusive access, ensuring thread safety.
     * This method will keep trying to obtain a lock until it succeeds or times out after 10 seconds.
     *
     * @return The value indicating the locked wire's index.
     * @throws IllegalStateException if the locking attempt times out.
     */
    public int lock() throws IllegalStateException {
        long start = System.currentTimeMillis();
        for (; ; Jvm.nanoPause()) {
            int val = value;
            if ((val & LOCKED) != 0) {
                if (System.currentTimeMillis() > start + 10_000)
                    throw new IllegalStateException("timeout");
                continue;
            }
            int writeTo = val & USED_MASK;
            int val2 = LOCKED | writeTo;
            if (MEMORY.compareAndSwapInt(this, valueOffset, val, val2)) {
                return val2;
            }
        }
    }

    /**
     * Releases the currently locked consumer wire.
     * This method is currently a placeholder and may be needed for future implementations.
     */
    public void releaseConsumer() {
        // may be needed in the future
    }

    @Override
    public DocumentContext writingDocument(boolean metaData) {
        final Wire wire = acquireProducer();
        this.writeContext.wire(wire);
        this.writeContext.start(metaData);
        return this.writeContext;
    }

    @Override
    public DocumentContext acquireWritingDocument(boolean metaData) {
        return this.writeContext.documentContext() != null
                && this.writeContext.isOpen()
                && this.writeContext.chainedElement()
                ? this.writeContext
                : this.writingDocument(metaData);
    }

    /**
     * The WEDocumentContext class extends DocumentContextHolder and implements the WriteDocumentContext interface.
     * This class provides a context for writing to a document within the WireExchanger and handles the
     * lifecycle of the document, including its chaining state and closure.
     */
    class WEDocumentContext extends DocumentContextHolder implements WriteDocumentContext {

        // The Wire instance associated with this document context
        private Wire wire;

        @Override
        public void start(boolean metaData) {
            documentContext(wire.writingDocument(metaData));
        }

        @Override
        public boolean chainedElement() {
            return documentContext().chainedElement();
        }

        @Override
        public void chainedElement(boolean chainedElement) {
            documentContext().chainedElement(chainedElement);
        }

        @Override
        public WriteDocumentContext documentContext() {
            return (WriteDocumentContext) super.documentContext();
        }

        @Override
        public void close() {
            final WriteDocumentContext dc = documentContext();
            dc.close();
            if (!dc.isNotComplete()) {
                documentContext(null);
                releaseProducer();
            }
        }

        /**
         * Sets the Wire instance associated with this document context.
         *
         * @param wire The Wire instance to be associated.
         */
        public void wire(Wire wire) {
            this.wire = wire;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy