├── README.md ├── src ├── test │ ├── resources │ │ ├── unbounded.conf │ │ ├── bounded.conf │ │ └── disruptor.conf │ └── java │ │ └── akka │ │ └── dispatch │ │ └── disruptor │ │ └── benchmark │ │ └── ThroughputBenchmark.java └── main │ └── java │ └── akka │ └── dispatch │ └── disruptor │ ├── DisruptorMessageQueueSemantics.java │ ├── DisruptorMailbox.java │ └── DisruptorMessageQueue.java ├── LICENSE └── pom.xml /README.md: -------------------------------------------------------------------------------- 1 | akka-disruptor 2 | ====== 3 | 4 | Akka bounded mailbox implementation using LMAX Disruptor 5 | -------------------------------------------------------------------------------- /src/test/resources/unbounded.conf: -------------------------------------------------------------------------------- 1 | dispatcher { 2 | mailbox = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" 3 | fork-join-executor { 4 | parallelism-min = 8 5 | parallelism-max = 8 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/bounded.conf: -------------------------------------------------------------------------------- 1 | dispatcher1 { 2 | mailbox-requirement = "akka.dispatch.BoundedMessageQueueSemantics" 3 | fork-join-executor { 4 | parallelism-min = 4 5 | parallelism-max = 4 6 | } 7 | } 8 | dispatcher2 { 9 | mailbox = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" 10 | fork-join-executor { 11 | parallelism-min = 4 12 | parallelism-max = 4 13 | } 14 | } 15 | akka.actor.default-mailbox { 16 | mailbox-capacity = 1024 17 | } 18 | -------------------------------------------------------------------------------- /src/test/resources/disruptor.conf: -------------------------------------------------------------------------------- 1 | dispatcher1 { 2 | mailbox-requirement = "akka.dispatch.disruptor.DisruptorMessageQueueSemantics" 3 | fork-join-executor { 4 | parallelism-min = 4 5 | parallelism-max = 4 6 | } 7 | } 8 | dispatcher2 { 9 | mailbox = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" 10 | fork-join-executor { 11 | parallelism-min = 4 12 | parallelism-max = 4 13 | } 14 | } 15 | akka.actor.mailbox.requirements { 16 | "akka.dispatch.disruptor.DisruptorMessageQueueSemantics" = disruptor-mailbox 17 | } 18 | disruptor-mailbox { 19 | mailbox-type = "akka.dispatch.disruptor.DisruptorMailbox" 20 | mailbox-capacity = 1024 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Igor Konev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.akka-disruptor 8 | akka-disruptor 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | com.typesafe.akka 14 | akka-actor_2.10 15 | 2.2.3 16 | 17 | 18 | com.lmax 19 | disruptor 20 | 3.2.0 21 | 22 | 23 | junit 24 | junit 25 | 4.11 26 | test 27 | 28 | 29 | com.carrotsearch 30 | junit-benchmarks 31 | 0.7.2 32 | test 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/akka/dispatch/disruptor/DisruptorMessageQueueSemantics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2013 Igor Konev 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package akka.dispatch.disruptor; 26 | 27 | public interface DisruptorMessageQueueSemantics { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/akka/dispatch/disruptor/DisruptorMailbox.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2013 Igor Konev 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package akka.dispatch.disruptor; 26 | 27 | import akka.actor.ActorRef; 28 | import akka.actor.ActorSystem; 29 | import akka.dispatch.MailboxType; 30 | import akka.dispatch.MessageQueue; 31 | import akka.dispatch.ProducesMessageQueue; 32 | import com.lmax.disruptor.SleepingWaitStrategy; 33 | import com.typesafe.config.Config; 34 | import scala.Option; 35 | 36 | import static akka.actor.ActorSystem.Settings; 37 | 38 | public final class DisruptorMailbox implements MailboxType, ProducesMessageQueue { 39 | 40 | private final int bufferSize; 41 | 42 | public DisruptorMailbox(Settings settings, Config config) { 43 | int capacity = config.getInt("mailbox-capacity"); 44 | if (capacity < 1) { 45 | throw new IllegalArgumentException("Mailbox capacity must not be less than 1"); 46 | } 47 | if (Integer.bitCount(capacity) != 1) { 48 | throw new IllegalArgumentException("Mailbox capacity must be a power of 2"); 49 | } 50 | bufferSize = capacity; 51 | } 52 | 53 | @Override 54 | public MessageQueue create(Option owner, Option system) { 55 | return new DisruptorMessageQueue(bufferSize, new SleepingWaitStrategy()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/akka/dispatch/disruptor/DisruptorMessageQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2013 Igor Konev 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package akka.dispatch.disruptor; 26 | 27 | import akka.actor.ActorRef; 28 | import akka.dispatch.Envelope; 29 | import akka.dispatch.MessageQueue; 30 | import com.lmax.disruptor.AlertException; 31 | import com.lmax.disruptor.EventFactory; 32 | import com.lmax.disruptor.RingBuffer; 33 | import com.lmax.disruptor.Sequence; 34 | import com.lmax.disruptor.SequenceBarrier; 35 | import com.lmax.disruptor.TimeoutException; 36 | import com.lmax.disruptor.WaitStrategy; 37 | 38 | final class DisruptorMessageQueue implements MessageQueue, DisruptorMessageQueueSemantics { 39 | 40 | private static final EventFactory EVENT_FACTORY = new EventFactory() { 41 | 42 | @Override 43 | public ValueEvent newInstance() { 44 | return new ValueEvent(); 45 | } 46 | }; 47 | 48 | private final Sequence sequence = new Sequence(); 49 | private final RingBuffer ringBuffer; 50 | private final SequenceBarrier sequenceBarrier; 51 | private final int mask; 52 | private final Envelope[] buffer; 53 | private int head; 54 | private int tail; 55 | 56 | DisruptorMessageQueue(int bufferSize, WaitStrategy waitStrategy) { 57 | ringBuffer = RingBuffer.createMultiProducer(EVENT_FACTORY, bufferSize, waitStrategy); 58 | ringBuffer.addGatingSequences(sequence); 59 | sequenceBarrier = ringBuffer.newBarrier(); 60 | mask = bufferSize - 1; 61 | buffer = new Envelope[bufferSize]; 62 | } 63 | 64 | @Override 65 | public void enqueue(ActorRef receiver, Envelope handle) { 66 | long nextSequence = ringBuffer.next(); 67 | ringBuffer.get(nextSequence).handle = handle; 68 | ringBuffer.publish(nextSequence); 69 | } 70 | 71 | @Override 72 | public Envelope dequeue() { 73 | fill(); 74 | int h = head & mask; 75 | head++; 76 | Envelope handle = buffer[h]; 77 | buffer[h] = null; 78 | return handle; 79 | } 80 | 81 | @Override 82 | public int numberOfMessages() { 83 | return 0; 84 | } 85 | 86 | @Override 87 | public boolean hasMessages() { 88 | return head != tail || sequence.get() < sequenceBarrier.getCursor(); 89 | } 90 | 91 | @Override 92 | public void cleanUp(ActorRef owner, MessageQueue deadLetters) { 93 | while (hasMessages()) { 94 | deadLetters.enqueue(owner, dequeue()); 95 | } 96 | } 97 | 98 | private void fill() { 99 | if (head != tail) { 100 | return; 101 | } 102 | 103 | long nextSequence = sequence.get() + 1L; 104 | boolean interrupted = false; 105 | long availableSequence; 106 | try { 107 | while (true) { 108 | try { 109 | availableSequence = sequenceBarrier.waitFor(nextSequence); 110 | if (nextSequence <= availableSequence) { 111 | break; 112 | } 113 | Thread.yield(); 114 | } catch (AlertException ignored) { 115 | interrupted = true; 116 | } catch (InterruptedException ignored) { 117 | interrupted = true; 118 | } catch (TimeoutException ignored) { 119 | } 120 | } 121 | } finally { 122 | if (interrupted) { 123 | Thread.currentThread().interrupt(); 124 | } 125 | } 126 | 127 | int t = tail; 128 | do { 129 | buffer[t & mask] = ringBuffer.get(nextSequence).handle; 130 | t++; 131 | nextSequence++; 132 | } while (nextSequence <= availableSequence); 133 | 134 | tail = t; 135 | sequence.set(availableSequence); 136 | } 137 | 138 | private static final class ValueEvent { 139 | 140 | Envelope handle; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/java/akka/dispatch/disruptor/benchmark/ThroughputBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2013 Igor Konev 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package akka.dispatch.disruptor.benchmark; 26 | 27 | import akka.actor.ActorRef; 28 | import akka.actor.ActorSystem; 29 | import akka.actor.Props; 30 | import akka.actor.UntypedActor; 31 | import com.carrotsearch.junitbenchmarks.AbstractBenchmark; 32 | import com.typesafe.config.Config; 33 | import com.typesafe.config.ConfigFactory; 34 | import java.util.Arrays; 35 | import java.util.List; 36 | import java.util.concurrent.CountDownLatch; 37 | import org.junit.After; 38 | import org.junit.Before; 39 | import org.junit.FixMethodOrder; 40 | import org.junit.Test; 41 | import org.junit.runner.RunWith; 42 | import org.junit.runners.Parameterized; 43 | 44 | import static org.junit.runners.Parameterized.Parameters; 45 | 46 | @RunWith(Parameterized.class) 47 | @FixMethodOrder 48 | public class ThroughputBenchmark extends AbstractBenchmark { 49 | 50 | private final int numberOfClients; 51 | private final long repeats; 52 | private final Config config; 53 | private final String dispatcher1; 54 | private final String dispatcher2; 55 | private ActorSystem system; 56 | private CountDownLatch latch; 57 | private ActorRef[] destinations; 58 | private ActorRef[] clients; 59 | 60 | public ThroughputBenchmark(int numberOfClients, long repeats, Config config, String dispatcher1, 61 | String dispatcher2) { 62 | this.numberOfClients = numberOfClients; 63 | this.repeats = repeats; 64 | this.config = config; 65 | this.dispatcher1 = dispatcher1; 66 | this.dispatcher2 = dispatcher2; 67 | } 68 | 69 | @Before 70 | public void setUp() throws Exception { 71 | system = ActorSystem.create("MySystem", config); 72 | latch = new CountDownLatch(numberOfClients); 73 | destinations = new ActorRef[numberOfClients]; 74 | clients = new ActorRef[numberOfClients]; 75 | 76 | Props props = Props.create(Destination.class).withDispatcher(dispatcher1); 77 | long repeatsPerClient = repeats / numberOfClients; 78 | 79 | for (int i = 0; i < numberOfClients; i++) { 80 | ActorRef destination = system.actorOf(props); 81 | destinations[i] = destination; 82 | clients[i] = system.actorOf(Props.create(Client.class, destination, latch, repeatsPerClient) 83 | .withDispatcher(dispatcher2)); 84 | } 85 | } 86 | 87 | @After 88 | public void tearDown() throws Exception { 89 | for (int i = 0; i < numberOfClients; i++) { 90 | system.stop(clients[i]); 91 | system.stop(destinations[i]); 92 | } 93 | } 94 | 95 | @Test 96 | public void test() throws InterruptedException { 97 | for (int i = 0; i < numberOfClients; i++) { 98 | clients[i].tell(Run.INSTANCE, null); 99 | } 100 | latch.await(); 101 | } 102 | 103 | @Parameters 104 | public static List parameters() { 105 | long repeats = 10000000L; 106 | Config config1 = ConfigFactory.load("disruptor.conf"); 107 | Config config2 = ConfigFactory.load("bounded.conf"); 108 | Config config3 = ConfigFactory.load("unbounded.conf"); 109 | return Arrays.asList(new Object[][]{ 110 | {1, repeats, config1, "dispatcher1", "dispatcher2"}, 111 | {2, repeats, config1, "dispatcher1", "dispatcher2"}, 112 | {4, repeats, config1, "dispatcher1", "dispatcher2"}, 113 | {8, repeats, config1, "dispatcher1", "dispatcher2"}, 114 | {16, repeats, config1, "dispatcher1", "dispatcher2"}, 115 | {1, repeats, config2, "dispatcher1", "dispatcher2"}, 116 | {2, repeats, config2, "dispatcher1", "dispatcher2"}, 117 | {4, repeats, config2, "dispatcher1", "dispatcher2"}, 118 | {8, repeats, config2, "dispatcher1", "dispatcher2"}, 119 | {16, repeats, config2, "dispatcher1", "dispatcher2"}, 120 | {1, repeats, config3, "dispatcher", "dispatcher"}, 121 | {2, repeats, config3, "dispatcher", "dispatcher"}, 122 | {4, repeats, config3, "dispatcher", "dispatcher"}, 123 | {8, repeats, config3, "dispatcher", "dispatcher"}, 124 | {16, repeats, config3, "dispatcher", "dispatcher"} 125 | }); 126 | } 127 | 128 | private static final class Destination extends UntypedActor { 129 | 130 | @Override 131 | public void onReceive(Object message) throws Exception { 132 | getSender().tell(message, getSelf()); 133 | } 134 | } 135 | 136 | private static final class Client extends UntypedActor { 137 | 138 | private final ActorRef destination; 139 | private final CountDownLatch latch; 140 | private final long repeats; 141 | private long sent; 142 | private long received; 143 | 144 | Client(ActorRef destination, CountDownLatch latch, long repeats) { 145 | this.destination = destination; 146 | this.latch = latch; 147 | this.repeats = repeats; 148 | } 149 | 150 | @Override 151 | public void onReceive(Object message) throws Exception { 152 | if (message instanceof Msg) { 153 | received++; 154 | if (sent < repeats) { 155 | destination.tell(Msg.INSTANCE, getSelf()); 156 | sent++; 157 | } else if (received >= repeats) { 158 | latch.countDown(); 159 | } 160 | } else if (message instanceof Run) { 161 | long n = Math.min(1000L, repeats); 162 | for (long i = 0L; i < n; i++) { 163 | destination.tell(Msg.INSTANCE, getSelf()); 164 | sent++; 165 | } 166 | } 167 | } 168 | } 169 | 170 | private static final class Msg { 171 | 172 | static final Msg INSTANCE = new Msg(); 173 | } 174 | 175 | private static final class Run { 176 | 177 | static final Run INSTANCE = new Run(); 178 | } 179 | } 180 | --------------------------------------------------------------------------------