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

qunar.tc.qmq.concurrent.ActorSystem Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2018 Qunar, Inc.
 *
 * 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 qunar.tc.qmq.concurrent;

import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qunar.tc.qmq.monitor.QMon;

import java.lang.reflect.Field;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by zhaohui.yu
 * 16/4/8
 */
public class ActorSystem {
    private static final Logger LOG = LoggerFactory.getLogger(ActorSystem.class);

    private static final int DEFAULT_QUEUE_SIZE = 10000;

    private final ConcurrentMap actors;
    private final ThreadPoolExecutor executor;
    private final AtomicInteger actorsCount;
    private final String name;

    public ActorSystem(String name) {
        this(name, Runtime.getRuntime().availableProcessors() * 4, true);
    }

    public ActorSystem(String name, int threads, boolean fair) {
        this.name = name;
        this.actorsCount = new AtomicInteger();
        BlockingQueue queue = fair ? new PriorityBlockingQueue<>() : new LinkedBlockingQueue<>();
        this.executor = new ThreadPoolExecutor(threads, threads, 60, TimeUnit.MINUTES, queue, new NamedThreadFactory("actor-sys-" + name));
        this.actors = Maps.newConcurrentMap();
        QMon.dispatchersGauge(name, actorsCount::doubleValue);
        QMon.actorSystemQueueGauge(name, () -> (double) executor.getQueue().size());
    }

    public  void dispatch(String actorPath, E msg, Processor processor) {
        Actor actor = createOrGet(actorPath, processor);
        actor.dispatch(msg);
        schedule(actor, true);
    }

    public void suspend(String actorPath) {
        Actor actor = actors.get(actorPath);
        if (actor == null) return;

        actor.suspend();
    }

    public void resume(String actorPath) {
        Actor actor = actors.get(actorPath);
        if (actor == null) return;

        actor.resume();
        schedule(actor, false);
    }

    private  Actor createOrGet(String actorPath, Processor processor) {
        Actor actor = actors.get(actorPath);
        if (actor != null) return actor;

        Actor add = new Actor<>(this.name, actorPath, this, processor, DEFAULT_QUEUE_SIZE);
        Actor old = actors.putIfAbsent(actorPath, add);
        if (old == null) {
            LOG.info("create actorSystem: {}", actorPath);
            actorsCount.incrementAndGet();
            return add;
        }
        return old;
    }

    private  boolean schedule(Actor actor, boolean hasMessageHint) {
        if (!actor.canBeSchedule(hasMessageHint)) return false;
        if (actor.setAsScheduled()) {
            actor.submitTs = System.currentTimeMillis();
            this.executor.execute(actor);
            return true;
        }
        return false;
    }

    public interface Processor {
        boolean process(T message, Actor self);
    }

    public static class Actor implements Runnable, Comparable {
        private static final int Open = 0;
        private static final int Scheduled = 2;
        private static final int shouldScheduleMask = 3;
        private static final int shouldNotProcessMask = ~2;
        private static final int suspendUnit = 4;
        //每个actor至少执行的时间片
        private static final int QUOTA = 5;
        private static long statusOffset;

        static {
            try {
                statusOffset = Unsafe.instance.objectFieldOffset(Actor.class.getDeclaredField("status"));
            } catch (Throwable t) {
                throw new ExceptionInInitializerError(t);
            }
        }

        final String systemName;
        final ActorSystem actorSystem;
        final BoundedNodeQueue queue;
        final Processor processor;
        private final String name;
        private long total;
        private volatile long submitTs;
        //通过Unsafe操作
        private volatile int status;

        Actor(String systemName, String name, ActorSystem actorSystem, Processor processor, final int queueSize) {
            this.systemName = systemName;
            this.name = name;
            this.actorSystem = actorSystem;
            this.processor = processor;
            this.queue = new BoundedNodeQueue<>(queueSize);

            QMon.actorQueueGauge(systemName, name, () -> (double) queue.count());
        }

        boolean dispatch(E message) {
            return queue.add(message);
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            String old = Thread.currentThread().getName();
            try {
                Thread.currentThread().setName(systemName + "-" + name);
                if (shouldProcessMessage()) {
                    processMessages();
                }
            } finally {
                long duration = System.currentTimeMillis() - start;
                total += duration;
                QMon.actorProcessTime(name, duration);

                Thread.currentThread().setName(old);
                setAsIdle();
                this.actorSystem.schedule(this, false);
            }
        }

        void processMessages() {
            long deadline = System.currentTimeMillis() + QUOTA;
            while (true) {
                E message = queue.peek();
                if (message == null) return;
                boolean process = processor.process(message, this);
                if (!process) return;

                queue.pollNode();
                if (System.currentTimeMillis() >= deadline) return;
            }
        }

        final boolean shouldProcessMessage() {
            return (currentStatus() & shouldNotProcessMask) == 0;
        }

        private boolean canBeSchedule(boolean hasMessageHint) {
            int s = currentStatus();
            if (s == Open || s == Scheduled) return hasMessageHint || !queue.isEmpty();
            return false;
        }

        public final boolean resume() {
            while (true) {
                int s = currentStatus();
                int next = s < suspendUnit ? s : s - suspendUnit;
                if (updateStatus(s, next)) return next < suspendUnit;
            }
        }

        public final void suspend() {
            while (true) {
                int s = currentStatus();
                if (updateStatus(s, s + suspendUnit)) return;
            }
        }

        final boolean setAsScheduled() {
            while (true) {
                int s = currentStatus();
                if ((s & shouldScheduleMask) != Open) return false;
                if (updateStatus(s, s | Scheduled)) return true;
            }
        }

        final void setAsIdle() {
            while (true) {
                int s = currentStatus();
                if (updateStatus(s, s & ~Scheduled)) return;
            }
        }

        final int currentStatus() {
            return Unsafe.instance.getIntVolatile(this, statusOffset);
        }

        private boolean updateStatus(int oldStatus, int newStatus) {
            return Unsafe.instance.compareAndSwapInt(this, statusOffset, oldStatus, newStatus);
        }

        @Override
        public int compareTo(Actor o) {
            int result = Long.compare(total, o.total);
            return result == 0 ? Long.compare(submitTs, o.submitTs) : result;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Actor actor = (Actor) o;
            return Objects.equals(systemName, actor.systemName) &&
                    Objects.equals(name, actor.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(systemName, name);
        }
    }

    /**
     * Copyright (C) 2009-2016 Lightbend Inc. 
     */

    /**
     * Lock-free bounded non-blocking multiple-producer single-consumer queue based on the works of:
     * 

* Andriy Plokhotnuyk (https://github.com/plokhotnyuk) * - https://github.com/plokhotnyuk/actors/blob/2e65abb7ce4cbfcb1b29c98ee99303d6ced6b01f/src/test/scala/akka/dispatch/Mailboxes.scala * (Apache V2: https://github.com/plokhotnyuk/actors/blob/master/LICENSE) *

* Dmitriy Vyukov's non-intrusive MPSC queue: * - http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue * (Simplified BSD) */ @SuppressWarnings("serial") private static class BoundedNodeQueue { private final static long enqOffset, deqOffset; static { try { enqOffset = Unsafe.instance.objectFieldOffset(BoundedNodeQueue.class.getDeclaredField("_enqDoNotCallMeDirectly")); deqOffset = Unsafe.instance.objectFieldOffset(BoundedNodeQueue.class.getDeclaredField("_deqDoNotCallMeDirectly")); } catch (Throwable t) { throw new ExceptionInInitializerError(t); } } private final int capacity; @SuppressWarnings("unused") private volatile Node _enqDoNotCallMeDirectly; @SuppressWarnings("unused") private volatile Node _deqDoNotCallMeDirectly; protected BoundedNodeQueue(final int capacity) { if (capacity < 0) throw new IllegalArgumentException("AbstractBoundedNodeQueue.capacity must be >= 0"); this.capacity = capacity; final Node n = new Node(); setDeq(n); setEnq(n); } @SuppressWarnings("unchecked") private Node getEnq() { return (Node) Unsafe.instance.getObjectVolatile(this, enqOffset); } private void setEnq(Node n) { Unsafe.instance.putObjectVolatile(this, enqOffset, n); } private boolean casEnq(Node old, Node nju) { return Unsafe.instance.compareAndSwapObject(this, enqOffset, old, nju); } @SuppressWarnings("unchecked") private Node getDeq() { return (Node) Unsafe.instance.getObjectVolatile(this, deqOffset); } private void setDeq(Node n) { Unsafe.instance.putObjectVolatile(this, deqOffset, n); } private boolean casDeq(Node old, Node nju) { return Unsafe.instance.compareAndSwapObject(this, deqOffset, old, nju); } public final int count() { final Node lastNode = getEnq(); final int lastNodeCount = lastNode.count; return lastNodeCount - getDeq().count; } /** * @return the maximum capacity of this queue */ public final int capacity() { return capacity; } // Possible TODO — impl. could be switched to addNode(new Node(value)) if we want to allocate even if full already public final boolean add(final T value) { for (Node n = null; ; ) { final Node lastNode = getEnq(); final int lastNodeCount = lastNode.count; if (lastNodeCount - getDeq().count < capacity) { // Trade a branch for avoiding to create a new node if full, // and to avoid creating multiple nodes on write conflict á la Be Kind to Your GC if (n == null) { n = new Node(); n.value = value; } n.count = lastNodeCount + 1; // Piggyback on the HB-edge between getEnq() and casEnq() // Try to putPullLogs the node to the end, if we fail we continue loopin' if (casEnq(lastNode, n)) { lastNode.setNext(n); return true; } } else return false; // Over capacity—couldn't add the node } } public final boolean isEmpty() { return getEnq() == getDeq(); } /** * Removes the first element of this queue if any * * @return the value of the first element of the queue, null if empty */ public final T poll() { final Node n = pollNode(); return (n != null) ? n.value : null; } public final T peek() { Node n = peekNode(); return (n != null) ? n.value : null; } @SuppressWarnings("unchecked") protected final Node peekNode() { for (; ; ) { final Node deq = getDeq(); final Node next = deq.next(); if (next != null || getEnq() == deq) return next; } } /** * Removes the first element of this queue if any * * @return the `Node` of the first element of the queue, null if empty */ public final Node pollNode() { for (; ; ) { final Node deq = getDeq(); final Node next = deq.next(); if (next != null) { if (casDeq(deq, next)) { deq.value = next.value; deq.setNext(null); next.value = null; return deq; } // else we retry (concurrent consumers) } else if (getEnq() == deq) return null; // If we got a null and head meets tail, we are empty } } public static class Node { private final static long nextOffset; static { try { nextOffset = Unsafe.instance.objectFieldOffset(Node.class.getDeclaredField("_nextDoNotCallMeDirectly")); } catch (Throwable t) { throw new ExceptionInInitializerError(t); } } protected T value; protected int count; @SuppressWarnings("unused") private volatile Node _nextDoNotCallMeDirectly; @SuppressWarnings("unchecked") public final Node next() { return (Node) Unsafe.instance.getObjectVolatile(this, nextOffset); } protected final void setNext(final Node newNext) { Unsafe.instance.putOrderedObject(this, nextOffset, newNext); } } } static class Unsafe { public final static sun.misc.Unsafe instance; static { try { sun.misc.Unsafe found = null; for (Field field : sun.misc.Unsafe.class.getDeclaredFields()) { if (field.getType() == sun.misc.Unsafe.class) { field.setAccessible(true); found = (sun.misc.Unsafe) field.get(null); break; } } if (found == null) throw new IllegalStateException("Can't find instance of sun.misc.Unsafe"); else instance = found; } catch (Throwable t) { throw new ExceptionInInitializerError(t); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy