├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── PhpDisruptor │ ├── AbstractSequencer.php │ ├── AggregateEventHandler.php │ ├── Collections │ └── Histogram.php │ ├── CursoredInterface.php │ ├── DataInterface.php │ ├── DataProviderInterface.php │ ├── Dsl │ ├── ConsumerInfoInterface.php │ ├── ConsumerRepository.php │ ├── Disruptor.php │ ├── EventHandlerGroup.php │ ├── EventProcessorInfo.php │ ├── ExceptionHandlerSetting.php │ ├── ProducerType.php │ └── WorkerPoolInfo.php │ ├── EventClassCapableInterface.php │ ├── EventFactoryInterface.php │ ├── EventHandlerInterface.php │ ├── EventProcessor │ ├── AbstractEventProcessor.php │ ├── BatchEventProcessor.php │ ├── NoOpEventProcessor.php │ └── WorkProcessor.php │ ├── EventTranslatorInterface.php │ ├── Exception │ ├── AlertException.php │ ├── ExceptionInterface.php │ ├── ExtensionNotLoadedException.php │ ├── InsufficientCapacityException.php │ ├── InterruptedException.php │ ├── InvalidArgumentException.php │ ├── RuntimeException.php │ ├── TimeoutException.php │ └── UnsupportedMethodCallException.php │ ├── ExceptionHandler │ ├── AbstractExceptionHandler.php │ ├── ExceptionHandlerInterface.php │ ├── FatalExceptionHandler.php │ └── IgnoreExceptionHandler.php │ ├── FixedSequenceGroup.php │ ├── LifecycleAwareInterface.php │ ├── Lists │ ├── EventHandlerList.php │ ├── EventProcessorList.php │ ├── EventTranslatorList.php │ ├── SequenceList.php │ └── WorkHandlerList.php │ ├── MultiProducerSequencer.php │ ├── ProcessingSequenceBarrier.php │ ├── RingBuffer.php │ ├── Sequence.php │ ├── SequenceAggregateInterface.php │ ├── SequenceBarrierInterface.php │ ├── SequenceGroup.php │ ├── SequenceGroups.php │ ├── SequenceReportingEventHandlerInterface.php │ ├── SequencerFollowingSequence.php │ ├── SequencerInterface.php │ ├── SingleProducerSequencer.php │ ├── TimeoutHandlerInterface.php │ ├── Util │ └── Util.php │ ├── WaitStrategy │ ├── BlockingWaitStrategy.php │ ├── BusySpinWaitStrategy.php │ ├── SleepingWaitStrategy.php │ ├── TimeoutBlockingWaitStrategy.php │ ├── WaitStrategyInterface.php │ └── YieldingWaitStrategy.php │ ├── WorkHandlerInterface.php │ └── WorkerPool.php └── tests ├── Bootstrap.php └── PhpDisruptorTest ├── AggregateEventHandler ├── AggregateEventHandlerTest.php └── TestAsset │ ├── EventHandler.php │ └── ResultCounter.php ├── Dsl ├── ConsumerRepository │ └── ConsumerRepositoryTest.php └── Disruptor │ ├── DisruptorTest.php │ └── TestAsset │ ├── DelayedEventHandler.php │ └── TestWorkHandler.php ├── EventProcessor └── BatchEventProcessor │ ├── BatchEventProcessorTest.php │ └── TestAsset │ ├── EventHandler.php │ ├── ExEventHandler.php │ └── TestExceptionHandler.php ├── EventPublisherTest.php ├── EventTranslatorTest.php ├── ExceptionHandler ├── FatalExceptionHandlerTest.php └── IgnoreExceptionHandlerTest.php ├── FixedSequenceGroupTest.php ├── LifecycleAwareInterface ├── LifecylceAwareInterfaceTest.php └── TestAsset │ └── LifecycleAwareEventHandler.php ├── MultiProducerSequencerTest.php ├── RingBufferTest.php ├── SequenceBarrierTest.php ├── SequenceGroupTest.php ├── SequenceReportingCallbackTest.php ├── TestAsset ├── ArrayEventTranslator.php ├── ArrayFactory.php ├── CountDownLatchSequence.php ├── EventTranslator.php ├── ExampleEventTranslator.php ├── LongEvent.php ├── LongEventFactory.php ├── LongWorkHandler.php ├── RingBufferThread.php ├── RingBufferThread2.php ├── SequenceBarrierThread.php ├── SleepingEventHandler.php ├── StubEvent.php ├── StubEventFactory.php ├── StubEventProcessor.php ├── StubEventProcessorThread.php ├── StubEventTranslator.php ├── TestEvent.php ├── TestEventFactory.php ├── TestEventProcessor.php ├── TestEventProcessor2.php └── TestSequenceReportingEventHandler.php ├── Util └── UtilTest.php └── WaitStrategy ├── AbstractWaitStrategyTestCase.php ├── BusySpinWaitStrategyTest.php ├── SleepingWaitStrategyTest.php ├── TestAsset ├── DummySequenceBarrier.php └── SequenceUpdater.php └── YieldingWaitStrategyTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | composer.lock 4 | composer.phar 5 | phpunit.xml 6 | phpmd.xml 7 | phpdox.xml 8 | .project 9 | nbproject -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | 8 | before_script: 9 | - git clone https://github.com/krakjoe/pthreads lib-pthreads 10 | - cd lib-pthreads 11 | - phpize 12 | - ./configure 13 | - make 14 | - sudo make install 15 | - cd .. 16 | - echo "extension = pthreads.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 17 | - composer self-update 18 | - composer update --prefer-source --dev 19 | 20 | script: 21 | - ./vendor/bin/phpunit 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Sascha-Oliver Prolic 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP Disruptor 2 | ========================= 3 | 4 | [![Build Status](https://travis-ci.org/prolic/PhpDisruptor.png?branch=master)](https://travis-ci.org/prolic/PhpDisruptor) 5 | [![Total Downloads](https://poser.pugx.org/prolic/php-disruptor/downloads.png)](https://packagist.org/packages/prolic/php-disruptor) 6 | [![Latest Stable Version](https://poser.pugx.org/prolic/php-disruptor/v/stable.png)](https://packagist.org/packages/prolic/php-disruptor) 7 | [![Latest Unstable Version](https://poser.pugx.org/prolic/php-disruptor/v/unstable.png)](https://packagist.org/packages/prolic/php-disruptor) 8 | [![Dependency Status](https://www.versioneye.com/php/prolic:php-disruptor/dev-master/badge.png)](https://www.versioneye.com/php/prolic:php-disruptor) 9 | 10 | A High Performance Inter-Thread Messaging Library 11 | 12 | Port of the [LMAX Disruptor](https://github.com/LMAX-Exchange/disruptor) 13 | 14 | Info 15 | ---- 16 | This project will is discontinued and archived. PHP's pthreads extensions has been archived already. 17 | 18 | 19 | Requirements 20 | ------------ 21 | 22 | - PHP 5.4 ZTS 23 | - ext-pthreads 0.0.45 24 | - ext-uuid 1.0.3 25 | - Concurrent-PHP-Utils 26 | - MabeEnum 27 | 28 | Under construction... 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prolic/php-disruptor", 3 | "type": "library", 4 | "description": "A High Performance Inter-Thread Messaging Library", 5 | "keywords": ["disruptor" , "php"], 6 | "homepage": "https://github.com/prolic/PhpDisruptor", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Sascha-Oliver Prolic", 11 | "email": "saschaprolic@googlemail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.4.0", 16 | "ext-pthreads": ">=2.0.8", 17 | "marc-mabe/php-enum": "1.2.*", 18 | "prolic/concurrent-php-utils": "dev-master" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": ">=3.7" 22 | }, 23 | "autoload": { 24 | "psr-0": { 25 | "PhpDisruptor\\": "src" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | ./tests/PhpDisruptorTest 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/PhpDisruptor/AbstractSequencer.php: -------------------------------------------------------------------------------- 1 | bitCount($bufferSize) != 1) { 49 | throw new Exception\InvalidArgumentException('$bufferSize must be a power of 2'); 50 | } 51 | 52 | $this->bufferSize = $bufferSize; 53 | $this->waitStrategy = $waitStrategy; 54 | $this->sequences = new SequenceList(); 55 | $this->cursor = new Sequence(); 56 | } 57 | 58 | /** 59 | * @param int $n 60 | * @return int 61 | */ 62 | public function bitCount($n) 63 | { 64 | $count = 0; 65 | while ($n != 0) { 66 | $count++; 67 | $n &= ($n - 1); 68 | } 69 | return $count; 70 | } 71 | 72 | /** 73 | * Returns the gating sequences 74 | * 75 | * @return SequenceList 76 | */ 77 | public function getSequences() 78 | { 79 | return $this->sequences; 80 | } 81 | 82 | /** 83 | * Get cursor 84 | * 85 | * @return int 86 | */ 87 | public function getCursor() 88 | { 89 | return $this->cursor->get(); 90 | } 91 | 92 | /** 93 | * The capacity of the data structure to hold entries. 94 | * 95 | * @return int the size of the RingBuffer. 96 | */ 97 | public function getBufferSize() 98 | { 99 | return $this->bufferSize; 100 | } 101 | 102 | /** 103 | * @param SequenceList $gatingSequences 104 | * @return void 105 | */ 106 | public function addGatingSequences(SequenceList $gatingSequences) 107 | { 108 | SequenceGroups::addSequences($this, $this, $gatingSequences); 109 | } 110 | 111 | /** 112 | * @param Sequence $sequence 113 | * @return bool|void 114 | */ 115 | public function removeGatingSequence(Sequence $sequence) 116 | { 117 | return SequenceGroups::removeSequence($this, $sequence); 118 | } 119 | 120 | /** 121 | * @inheritdoc 122 | */ 123 | public function getMinimumSequence() 124 | { 125 | return Util::getMinimumSequence($this->getSequences(), $this->cursor->get()); 126 | } 127 | 128 | /** 129 | * @param SequenceList|null $sequencesToTrack 130 | * @return ProcessingSequenceBarrier 131 | */ 132 | public function newBarrier(SequenceList $sequencesToTrack = null) 133 | { 134 | if (null === $sequencesToTrack) { 135 | $sequencesToTrack = new SequenceList(); 136 | } 137 | $barrier = new ProcessingSequenceBarrier($this, $this->waitStrategy, $this->cursor, $sequencesToTrack); 138 | return $barrier; 139 | } 140 | 141 | /** 142 | * @inheritdoc 143 | */ 144 | public function casSequences(SequenceList $oldSequences, SequenceList $newSequences) 145 | { 146 | return Util::casSequences($this, $oldSequences, $newSequences); 147 | } 148 | 149 | /** 150 | * @param SequenceList $sequences 151 | * @return void 152 | */ 153 | public function setSequences(SequenceList $sequences) 154 | { 155 | $this->sequences = $sequences; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/PhpDisruptor/AggregateEventHandler.php: -------------------------------------------------------------------------------- 1 | eventClass = $eventClass; 35 | 36 | foreach ($eventHandlers as $eventHandler) { 37 | if ($eventHandler->getEventClass() != $eventClass 38 | ) { 39 | throw new Exception\InvalidArgumentException( 40 | 'all event handler must use the same event class as the aggregate event handler, ' 41 | . ' in this case: "' . $eventClass .'"' 42 | ); 43 | } 44 | } 45 | $this->eventHandlers = $eventHandlers; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getEventClass() 52 | { 53 | return $this->eventClass; 54 | } 55 | 56 | /** 57 | * Called when a publisher has published an event to the RingBuffer 58 | * 59 | * @param object $event published to the RingBuffer 60 | * @param int $sequence of the event being processed 61 | * @param bool $endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer 62 | * @return void 63 | * @throws Exception\ExceptionInterface if the EventHandler would like the exception handled further up the chain. 64 | */ 65 | public function onEvent($event, $sequence, $endOfBatch) 66 | { 67 | if (!is_object($event)) { 68 | throw new Exception\InvalidArgumentException('event must be an object'); 69 | } 70 | if (!is_numeric($sequence)) { 71 | throw new Exception\InvalidArgumentException('$sequence must be an integer'); 72 | } 73 | if (!is_bool($endOfBatch)) { 74 | throw new Exception\InvalidArgumentException('$endOfBatch must be a boolean'); 75 | } 76 | $eventClass = $this->getEventClass(); 77 | if (!$event instanceof $eventClass) { 78 | throw new Exception\InvalidArgumentException('$event must be an instance of ' . $eventClass); 79 | } 80 | 81 | foreach ($this->eventHandlers as $eventHandler) { 82 | $eventHandler->onEvent($event, $sequence, $endOfBatch); 83 | } 84 | } 85 | 86 | /** 87 | * Called once on thread start before first event is available. 88 | * 89 | * @return void 90 | */ 91 | public function onStart() 92 | { 93 | foreach ($this->eventHandlers as $eventHandler) { 94 | if ($eventHandler instanceof LifecycleAwareInterface) { 95 | $eventHandler->onStart(); 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * Called once just before the thread is shutdown. 102 | * 103 | * Sequence event processing will already have stopped before this method is called. No events will 104 | * be processed after this message. 105 | * 106 | * @return void 107 | */ 108 | public function onShutdown() 109 | { 110 | foreach ($this->eventHandlers as $eventHandler) { 111 | if ($eventHandler instanceof LifecycleAwareInterface) { 112 | $eventHandler->onShutdown(); 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/PhpDisruptor/CursoredInterface.php: -------------------------------------------------------------------------------- 1 | eventClass = $eventFactory->getEventClass(); 50 | $this->eventProcessorInfoByEventHandler = new ObjectStorage(); 51 | $this->eventProcessorInfoBySequence = new ObjectStorage(); 52 | $this->consumerInfos = new Threaded(); 53 | } 54 | 55 | /** 56 | * @inheritdoc 57 | */ 58 | public function getEventClass() 59 | { 60 | return $this->eventClass; 61 | } 62 | 63 | /** 64 | * @return ConsumerInfoInterface[] 65 | */ 66 | public function getConsumerInfos() 67 | { 68 | return $this->consumerInfos; 69 | } 70 | 71 | /** 72 | * Add event processor 73 | * 74 | * @param AbstractEventProcessor $eventProcessor 75 | * @param EventHandlerInterface $handler 76 | * @param SequenceBarrierInterface $barrier 77 | * @throws Exception\InvalidArgumentException 78 | */ 79 | public function addEventProcessor( 80 | AbstractEventProcessor $eventProcessor, 81 | EventHandlerInterface $handler = null, 82 | SequenceBarrierInterface $barrier = null 83 | ) { 84 | if ((null === $handler && null !== $barrier) 85 | || (null !== $handler && null === $barrier) 86 | ) { 87 | throw new Exception\InvalidArgumentException( 88 | 'Even $handler and $barrier are null or both of them not' 89 | ); 90 | } 91 | 92 | $consumerInfo = new EventProcessorInfo($eventProcessor, $handler, $barrier); 93 | $this->eventProcessorInfoBySequence->attach($eventProcessor->getSequence(), $consumerInfo); 94 | $this->consumerInfos[] = $consumerInfo; 95 | 96 | if (null !== $handler) { 97 | if ($handler->getEventClass() != $this->getEventClass()) { 98 | throw new Exception\InvalidArgumentException( 99 | 'Given event handler does not match current event class, current is "' 100 | . $this->getEventClass() . '", given "' . $handler->getEventClass() . '"' 101 | ); 102 | } 103 | $this->eventProcessorInfoByEventHandler->attach($handler, $consumerInfo); 104 | } 105 | } 106 | 107 | /** 108 | * Add worker pool 109 | * 110 | * @param WorkerPool $workerPool 111 | * @param SequenceBarrierInterface $sequenceBarrier 112 | */ 113 | public function addWorkerPool(WorkerPool $workerPool, SequenceBarrierInterface $sequenceBarrier) 114 | { 115 | $workerPoolInfo = new WorkerPoolInfo($workerPool, $sequenceBarrier); 116 | $this->consumerInfos[] = $workerPoolInfo; 117 | foreach ($workerPool->getWorkerSequences() as $sequence) { 118 | $this->eventProcessorInfoBySequence->attach($sequence, $workerPoolInfo); 119 | } 120 | } 121 | 122 | /** 123 | * Get last sequence in chain 124 | * 125 | * @param bool $includeStopped 126 | * @return Sequence[] 127 | */ 128 | public function getLastSequenceInChain($includeStopped) 129 | { 130 | $includeStopped = (bool) $includeStopped; 131 | $lastSequences = new SequenceList(); 132 | foreach ($this->consumerInfos as $consumerInfo) { 133 | if (($includeStopped || $consumerInfo->isRunning()) && $consumerInfo->isEndOfChain()) { 134 | $sequences = $consumerInfo->getSequences(); 135 | foreach ($sequences as $sequence) { 136 | $lastSequences[] = $sequence; 137 | } 138 | } 139 | } 140 | return $lastSequences; 141 | } 142 | 143 | /** 144 | * Get event processor for event handler 145 | * 146 | * @param EventHandlerInterface $handler 147 | * @return AbstractEventProcessor 148 | * @throws Exception\InvalidArgumentException 149 | */ 150 | public function getEventProcessorFor(EventHandlerInterface $handler) 151 | { 152 | $eventProcessorInfo = $this->_getEventProcessorInfo($handler); 153 | if (null === $eventProcessorInfo) { 154 | throw new Exception\InvalidArgumentException( 155 | 'The given event handler is not processing events' 156 | ); 157 | } 158 | return $eventProcessorInfo->getEventProcessor(); 159 | } 160 | 161 | /** 162 | * Get sequence for event handler 163 | * 164 | * @param EventHandlerInterface $handler 165 | * @return Sequence 166 | */ 167 | public function getSequenceFor(EventHandlerInterface $handler) 168 | { 169 | return $this->getEventProcessorFor($handler)->getSequence(); 170 | } 171 | 172 | /** 173 | * Un-mark event processors as end of chain 174 | * 175 | * @param Sequence[] $barrierEventProcessors 176 | * @throws Exception\InvalidArgumentException 177 | */ 178 | public function unMarkEventProcessorsAsEndOfChain(SequenceList $barrierEventProcessors = null) 179 | { 180 | if ($barrierEventProcessors === null) { 181 | return; 182 | } 183 | foreach ($barrierEventProcessors as $barrierEventProcessor) { 184 | $this->_getEventProcessorInfoBySequence($barrierEventProcessor)->markAsUsedInBarrier(); 185 | } 186 | } 187 | 188 | /** 189 | * Get barrier for event handler 190 | * 191 | * @param EventHandlerInterface $handler 192 | * @return SequenceBarrierInterface|null 193 | */ 194 | public function getBarrierFor(EventHandlerInterface $handler) 195 | { 196 | $consumerInfo = $this->_getEventProcessorInfo($handler); 197 | if (null === $consumerInfo) { 198 | return null; 199 | } 200 | return $consumerInfo->getBarrier(); 201 | } 202 | 203 | /** 204 | * Get event processor info by event handler 205 | * 206 | * @param EventHandlerInterface $handler 207 | * @return EventProcessorInfo 208 | */ 209 | public function _getEventProcessorInfo(EventHandlerInterface $handler) // public for pthreads reasons 210 | { 211 | foreach ($this->eventProcessorInfoByEventHandler->data as $key => $value) { 212 | if ($handler === $value) { 213 | return $this->eventProcessorInfoByEventHandler->info[$key]; 214 | } 215 | } 216 | } 217 | 218 | /** 219 | * Get event processor info by sequence 220 | * 221 | * @param Sequence $barrierEventProcessor 222 | * @return EventProcessorInfo 223 | */ 224 | public function _getEventProcessorInfoBySequence(Sequence $barrierEventProcessor) // public for pthreads reasons 225 | { 226 | foreach ($this->eventProcessorInfoBySequence->data as $key => $value) { 227 | if ($barrierEventProcessor === $value) { 228 | return $this->eventProcessorInfoBySequence->info[$key]; 229 | } 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Dsl/EventHandlerGroup.php: -------------------------------------------------------------------------------- 1 | getEventClass() != $consumerRepository->getEventClass()) { 50 | throw new Exception\InvalidArgumentException( 51 | '$consumerRepository uses event class ' . $consumerRepository->getEventClass() 52 | . ' but $disruptor uses event class ' . $disruptor->getEventClass() 53 | ); 54 | } 55 | $this->disruptor = $disruptor; 56 | $this->eventClass = $consumerRepository->getEventClass(); 57 | $this->consumerRepository = $consumerRepository; 58 | $this->sequences = $sequences; 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public function getEventClass() 65 | { 66 | return $this->eventClass; 67 | } 68 | 69 | 70 | /** 71 | * Create a new event handler group that combines the consumers in this group with otherHandlerGroup 72 | * 73 | * @param EventHandlerGroup $otherHandlerGroup the event handler group to combine 74 | * @return EventHandlerGroup a new EventHandlerGroup combining the existing and new consumers 75 | */ 76 | public function andEventHandlerGroup(self $otherHandlerGroup) 77 | { 78 | $combinedSequences = new SequenceList(); 79 | $combinedSequences->merge($this->sequences); 80 | $combinedSequences->merge($otherHandlerGroup->sequences); 81 | return new self($this->disruptor, $this->consumerRepository, $combinedSequences); 82 | } 83 | 84 | /** 85 | * Create a new event handler group that combines the handlers in this group with processors. 86 | * 87 | * @param EventProcessorList $processors the processors to combine. 88 | * @return EventHandlerGroup a new EventHandlerGroup combining the existing and new processors 89 | */ 90 | public function andProcessors(EventProcessorList $processors) 91 | { 92 | $combinedSequences = new SequenceList(); 93 | foreach ($processors as $processor) { 94 | $this->consumerRepository->addEventProcessor($processor); 95 | $combinedSequences[] = $processor->getSequence(); 96 | } 97 | $combinedSequences->merge($this->sequences); 98 | return new self($this->disruptor, $this->consumerRepository, $combinedSequences); 99 | } 100 | 101 | /** 102 | * Set up batch handlers to consume events from the ring buffer. These handlers will only process events 103 | * after every EventProcessor in this group has processed the event. 104 | * 105 | * This method is generally used as part of a chain. For example if the handler "A" must 106 | * process events before handler "B": 107 | * 108 | * $dw->handleEventsWith($A)->then($B); 109 | * 110 | * @param EventHandlerList $handlers the batch handlers that will process events. 111 | * @return EventHandlerGroup that can be used to set up a event processor barrier over the created event processors. 112 | */ 113 | public function then(EventHandlerList $handlers) 114 | { 115 | return $this->handleEventsWith($handlers); 116 | } 117 | 118 | /** 119 | * Set up a worker pool to handle events from the ring buffer. The worker pool will only process events 120 | * after every EventProcessor in this group has processed the event. Each event will be processed 121 | * by one of the work handler instances. 122 | * 123 | * This method is generally used as part of a chain. For example if the handler "A" 124 | * process events before the worker pool with handlers "B", "C": 125 | * 126 | * $dw->handleEventsWith($A)->thenHandleEventsWithWorkerPool($B, $C); 127 | * 128 | * @param WorkHandlerList $handlers the work handlers that will process events. 129 | * Each work handler instance will provide an extra thread in the worker pool. 130 | * @return EventHandlerGroup that can be used to set up a event processor barrier over the created event processors 131 | */ 132 | public function thenHandleEventsWithWorkerPool(WorkHandlerList $handlers) 133 | { 134 | return $this->handleEventsWithWorkerPool($handlers); 135 | } 136 | 137 | /** 138 | * Set up batch handlers to handle events from the ring buffer. These handlers will only process events 139 | * after every EventProcessor in this group has processed the event. 140 | * 141 | * This method is generally used as part of a chain. For example if the handler "A" must 142 | * process events before handler "B": 143 | * 144 | * $dw->after($A)->handleEventsWith($B); 145 | * 146 | * @param EventHandlerList $handlers the batch handlers that will process events. 147 | * @return EventHandlerGroup that can be used to set up a event processor barrier over the created event processors. 148 | */ 149 | public function handleEventsWith(EventHandlerList $handlers) 150 | { 151 | return $this->disruptor->createEventProcessors($this->sequences, $handlers); 152 | } 153 | 154 | /** 155 | * Set up a worker pool to handle events from the ring buffer. The worker pool will only process events 156 | * after every EventProcessor in this group has processed the event. Each event will be processed 157 | * by one of the work handler instances. 158 | * 159 | * This method is generally used as part of a chain. For example if the handler A must 160 | * process events before the worker pool with handlers B, C: 161 | * 162 | *
dw.after(A).handleEventsWithWorkerPool(B, C);
163 | * 164 | * @param WorkHandlerList $handlers the work handlers that will process events. 165 | * Each work handler instance will provide an extra thread in the worker pool. 166 | * @return EventHandlerGroup that can be used to set up a event processor barrier over the created event processors. 167 | */ 168 | public function handleEventsWithWorkerPool(WorkHandlerList $handlers) 169 | { 170 | return $this->disruptor->createWorkerPool($this->sequences, $handlers); 171 | } 172 | 173 | /** 174 | * Create a dependency barrier for the processors in this group. 175 | * This allows custom event processors to have dependencies on 176 | * BatchEventProcessors created by the disruptor. 177 | * 178 | * @return SequenceBarrierInterface including all the processors in this group. 179 | */ 180 | public function asSequenceBarrier() 181 | { 182 | return $this->disruptor->getRingBuffer()->newBarrier($this->sequences); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Dsl/EventProcessorInfo.php: -------------------------------------------------------------------------------- 1 | eventProcessor = $eventProcessor; 47 | $this->handler = $handler; 48 | $this->barrier = $barrier; 49 | $this->endOfChain = true; 50 | } 51 | 52 | /** 53 | * @return AbstractEventProcessor 54 | */ 55 | public function getEventProcessor() 56 | { 57 | return $this->eventProcessor; 58 | } 59 | 60 | /** 61 | * @return Sequence[] 62 | */ 63 | public function getSequences() 64 | { 65 | $sequence = $this->eventProcessor->getSequence(); 66 | $sequences = new SequenceList($sequence); 67 | return $sequences; 68 | } 69 | 70 | /** 71 | * @return EventHandlerInterface 72 | */ 73 | public function getHandler() 74 | { 75 | return $this->handler; 76 | } 77 | 78 | /** 79 | * @return SequenceBarrierInterface 80 | */ 81 | public function getBarrier() 82 | { 83 | return $this->barrier; 84 | } 85 | 86 | /** 87 | * @return bool 88 | */ 89 | public function isEndOfChain() 90 | { 91 | return $this->endOfChain; 92 | } 93 | 94 | public function start() 95 | { 96 | $this->eventProcessor->start(); 97 | } 98 | 99 | /** 100 | * @return void 101 | */ 102 | public function shutdown() 103 | { 104 | $this->eventProcessor->shutdown(); 105 | } 106 | 107 | /** 108 | * @return void 109 | */ 110 | public function markAsUsedInBarrier() 111 | { 112 | $this->endOfChain = false; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Dsl/ExceptionHandlerSetting.php: -------------------------------------------------------------------------------- 1 | getEventClass() != $consumerRepository->getEventClass()) { 38 | throw new Exception\InvalidArgumentException( 39 | '$consumerRepository uses event class ' . $consumerRepository->getEventClass() 40 | . ' but $eventHandler uses event class ' . $eventHandler->getEventClass() 41 | ); 42 | } 43 | $this->eventClass = $eventHandler->getEventClass(); 44 | $this->eventHandler = $eventHandler; 45 | $this->consumerRepository = $consumerRepository; 46 | } 47 | 48 | /** 49 | * @inheritdoc 50 | */ 51 | public function getEventClass() 52 | { 53 | return $this->eventClass; 54 | } 55 | 56 | /** 57 | * Specify the ExceptionHandler to use with the event handler 58 | * 59 | * @param ExceptionHandlerInterface $exceptionHandler 60 | * @return void 61 | */ 62 | public function with(ExceptionHandlerInterface $exceptionHandler) 63 | { 64 | $this->consumerRepository->getEventProcessorFor($this->eventHandler)->setExceptionHandler($exceptionHandler); 65 | $this->consumerRepository->getBarrierFor($this->eventHandler)->alert(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Dsl/ProducerType.php: -------------------------------------------------------------------------------- 1 | workerPool = $workerPool; 42 | $this->eventClass = $workerPool->getEventClass(); 43 | $this->sequenceBarrier = $sequenceBarrier; 44 | $this->endOfChain = true; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getEventClass() 51 | { 52 | return $this->eventClass; 53 | } 54 | 55 | /** 56 | * @return Sequence[] 57 | */ 58 | public function getSequences() 59 | { 60 | return $this->workerPool->getWorkerSequences(); 61 | } 62 | 63 | /** 64 | * @return SequenceBarrierInterface 65 | */ 66 | public function getBarrier() 67 | { 68 | return $this->sequenceBarrier; 69 | } 70 | 71 | /** 72 | * @return bool 73 | */ 74 | public function isEndOfChain() 75 | { 76 | return $this->endOfChain; 77 | } 78 | 79 | public function run() 80 | { 81 | $this->workerPool->start(); 82 | } 83 | 84 | /** 85 | * @return void 86 | */ 87 | public function halt() 88 | { 89 | $this->workerPool->halt(); 90 | } 91 | 92 | /** 93 | * @return void 94 | */ 95 | public function markAsUsedInBarrier() 96 | { 97 | $this->endOfChain = false; 98 | } 99 | 100 | /** 101 | * @return bool 102 | */ 103 | public function isRunning() 104 | { 105 | return $this->workerPool->isRunning(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/PhpDisruptor/EventClassCapableInterface.php: -------------------------------------------------------------------------------- 1 | getEventClass() != $eventClass) { 90 | throw new Exception\InvalidArgumentException( 91 | 'invalid data provider given, must use the event class: "' . $eventClass . '"' 92 | ); 93 | } 94 | if ($eventHandler->getEventClass() != $eventClass) { 95 | throw new Exception\InvalidArgumentException( 96 | 'invalid event handler given, must use the event class: "' . $eventClass . '"' 97 | ); 98 | } 99 | 100 | $this->eventClass = $eventClass; 101 | $this->dataProvider = $dataProvider; 102 | $this->sequencerBarrier = $sequenceBarrier; 103 | $this->sequence = new Sequence(SequencerInterface::INITIAL_CURSOR_VALUE); 104 | 105 | if ($eventHandler instanceof SequenceReportingEventHandlerInterface) { 106 | $eventHandler->setSequenceCallback($this->sequence); 107 | } 108 | $this->eventHandler = $eventHandler; 109 | 110 | $this->exceptionHandler = new FatalExceptionHandler('/tmp/disruptor-batchevents'); 111 | $this->timeoutHandler = ($eventHandler instanceof TimeoutHandlerInterface) ? $eventHandler : null; 112 | $this->running = false; 113 | } 114 | 115 | /** 116 | * @return Sequence 117 | */ 118 | public function getSequence() 119 | { 120 | return $this->sequence; 121 | } 122 | 123 | /** 124 | * @inheritdoc 125 | */ 126 | public function halt() 127 | { 128 | $this->running = false; 129 | $this->sequencerBarrier->alert(); 130 | } 131 | 132 | /** 133 | * @param ExceptionHandlerInterface $exceptionHandler 134 | */ 135 | public function setExceptionHandler(ExceptionHandlerInterface $exceptionHandler) 136 | { 137 | $this->exceptionHandler = $exceptionHandler; 138 | } 139 | 140 | public function run() 141 | { 142 | if (!$this->casMember('running', false, true)) { 143 | throw new Exception\RuntimeException( 144 | 'Thread is already running' 145 | ); 146 | } 147 | 148 | $this->sequencerBarrier->clearAlert(); 149 | 150 | // notify start 151 | if ($this->eventHandler instanceof LifecycleAwareInterface) { 152 | try { 153 | $this->eventHandler->onStart(); 154 | } catch (\Exception $e) { 155 | $this->exceptionHandler->handleOnStartException($e); 156 | } 157 | } 158 | 159 | $nextSequence = $this->getSequence()->get() + 1; 160 | while (true) { 161 | try { 162 | $availableSequence = $this->sequencerBarrier->waitFor($nextSequence); 163 | while ($nextSequence <= $availableSequence) { 164 | $event = $this->dataProvider->get($nextSequence); 165 | $this->eventHandler->onEvent($event, $nextSequence, $nextSequence == $availableSequence); 166 | $nextSequence++; 167 | } 168 | $this->getSequence()->set($availableSequence); 169 | } catch (Exception\TimeoutException $e) { 170 | // notify timeout 171 | $availableSequence = $this->getSequence()->get(); 172 | try { 173 | if (null !== $this->timeoutHandler) { 174 | $this->timeoutHandler->onTimeout($availableSequence); 175 | } 176 | } catch (\Exception $e) { 177 | $this->exceptionHandler->handleEventException($e, $availableSequence, null); 178 | } 179 | } catch (Exception\AlertException $e) { 180 | if (!$this->running) { 181 | break; 182 | } 183 | } catch (\Exception $e) { 184 | $event = isset($event) ? $event : ''; 185 | $this->exceptionHandler->handleEventException($e, $nextSequence, $event); 186 | $this->getSequence()->set($nextSequence); 187 | $nextSequence++; 188 | } 189 | } 190 | 191 | // notify shutdown 192 | if ($this->eventHandler instanceof LifecycleAwareInterface) { 193 | try { 194 | $this->eventHandler->onShutdown(); 195 | } catch (\Exception $e) { 196 | $this->exceptionHandler->handleOnShutdownException($e); 197 | } 198 | } 199 | $this->running = false; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/PhpDisruptor/EventProcessor/NoOpEventProcessor.php: -------------------------------------------------------------------------------- 1 | sequence = new SequencerFollowingSequence($sequencer); 29 | } 30 | 31 | /** 32 | * @return SequencerFollowingSequence 33 | */ 34 | public function getSequence() 35 | { 36 | return $this->sequence; 37 | } 38 | 39 | /** 40 | * @return void 41 | */ 42 | public function halt() 43 | { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/PhpDisruptor/EventProcessor/WorkProcessor.php: -------------------------------------------------------------------------------- 1 | getEventClass() != $this->ringBuffer->getEventClass()) { 63 | throw new Exception\InvalidArgumentException( 64 | 'All work handlers must use the event class as the ring buffer, buffer has "' 65 | . $this->ringBuffer->getEventClass() . '" and current handler has "' 66 | . $workHandler->getEventClass() . '"' 67 | ); 68 | } 69 | $this->sequence = new Sequence(); 70 | $this->ringBuffer = $ringBuffer; 71 | $this->sequenceBarrier = $sequenceBarrier; 72 | $this->workHandler = $workHandler; 73 | $this->exceptionHandler = $exceptionHandler; 74 | $this->workSequence = $workSequence; 75 | } 76 | 77 | /** 78 | * Get a reference to the Sequence being used by this EventProcessor. 79 | * 80 | * @return Sequence reference to the Sequence for this EventProcessor 81 | */ 82 | public function getSequence() 83 | { 84 | return $this->sequence; 85 | } 86 | 87 | /** 88 | * Signal that this EventProcessor should stop when it has finished consuming at the next clean break. 89 | * It will call {@link SequenceBarrierInterface#alert()} to notify the thread to check status. 90 | * 91 | * @return void 92 | * @throws Exception\RuntimeException 93 | */ 94 | public function halt() 95 | { 96 | $this->sequenceBarrier->clearAlert(); 97 | $this->_notifyStart(); 98 | 99 | $processedSequence = true; 100 | $cachedAvailableSequence = - PHP_INT_MAX - 1; 101 | $nextSequence = $this->sequence->get(); 102 | $event = null; 103 | while (true) { 104 | try { 105 | // if previous sequence was processed - fetch the next sequence and set 106 | // that we have successfully processed the previous sequence 107 | // typically, this will be true 108 | // this prevents the sequence getting too far forward if an exception 109 | // is thrown from the WorkHandler 110 | if ($processedSequence) { 111 | $processedSequence = false; 112 | $nextSequence = $this->workSequence->incrementAndGet(); 113 | $this->sequence->set($nextSequence - 1); 114 | } 115 | if ($cachedAvailableSequence >= $nextSequence) { 116 | $event = $this->ringBuffer->get($nextSequence); 117 | $this->workHandler->onEvent($event); 118 | $processedSequence = true; 119 | } else { 120 | $cachedAvailableSequence = $this->sequenceBarrier->waitFor($nextSequence); 121 | } 122 | } catch (Exception\AlertException $e) { 123 | if (!$this->isRunning()) { 124 | break; 125 | } 126 | } catch (\Exception $e) { 127 | $this->exceptionHandler->handleEventException($e, $nextSequence, $event); 128 | $processedSequence = true; 129 | } 130 | } 131 | 132 | $this->_notifyShutdown(); 133 | $this->shutdown(); 134 | } 135 | 136 | /** 137 | * @return void 138 | * @throws Exception\RuntimeException 139 | */ 140 | public function run() 141 | { 142 | $this->sequenceBarrier->clearAlert(); 143 | $this->_notifyStart(); 144 | 145 | $processedSequence = true; 146 | $cachedAvailableSequence = - PHP_INT_MAX - 1; 147 | $nextSequence = $this->sequence->get(); 148 | $event = null; 149 | 150 | while (true) { 151 | try { 152 | // if previous sequence was processed - fetch the next sequence and set 153 | // that we have successfully processed the previous sequence 154 | // typically, this will be true 155 | // this prevents the sequence getting too far forward if an exception 156 | // is thrown from the WorkHandler 157 | if ($processedSequence) { 158 | $processedSequence = false; 159 | $nextSequence = $this->workSequence->incrementAndGet(); 160 | $this->sequence->set($nextSequence - 1); 161 | } 162 | 163 | if ($cachedAvailableSequence >= $nextSequence) { 164 | $event = $this->ringBuffer->get($nextSequence); 165 | $this->workHandler->onEvent($event); 166 | $processedSequence = true; 167 | } else { 168 | $cachedAvailableSequence = $this->sequenceBarrier->waitFor($nextSequence); 169 | } 170 | } catch (Exception\AlertException $e) { 171 | if (!$this->isRunning()) { 172 | break; 173 | } 174 | } catch (\Exception $e) { 175 | $this->exceptionHandler->handleEventException($e, $nextSequence, $event); 176 | $processedSequence = true; 177 | } 178 | } 179 | 180 | $this->_notifyShutdown(); 181 | $this->running = false; 182 | } 183 | 184 | /** 185 | * @return void 186 | */ 187 | public function _notifyStart() // private !! only public for pthreads reasons 188 | { 189 | if ($this->workHandler instanceof LifecycleAwareInterface) { 190 | try { 191 | $this->workHandler->onStart(); 192 | } catch (\Exception $e) { 193 | $this->exceptionHandler->handleOnStartException($e); 194 | } 195 | } 196 | } 197 | 198 | /** 199 | * @return void 200 | */ 201 | public function _notifyShutdown() // private !! only public for pthreads reasons 202 | { 203 | if ($this->workHandler instanceof LifecycleAwareInterface) { 204 | try { 205 | $this->workHandler->onShutdown(); 206 | } catch (\Exception $e) { 207 | $this->exceptionHandler->handleOnShutdownException($e); 208 | } 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/PhpDisruptor/EventTranslatorInterface.php: -------------------------------------------------------------------------------- 1 | fh = fopen($file, 'a+b'); 30 | } 31 | 32 | public function __destruct() 33 | { 34 | fclose($this->fh); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/PhpDisruptor/ExceptionHandler/ExceptionHandlerInterface.php: -------------------------------------------------------------------------------- 1 | fh, 'ERR: Exception processing: ' . $sequence . ' ' . $event); 15 | throw new Exception\RuntimeException('', 0, $ex); 16 | } 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public function handleOnStartException(\Exception $ex) 22 | { 23 | fwrite($this->fh, 'ERR: Exception during onStart()'); 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public function handleOnShutdownException(\Exception $ex) 30 | { 31 | fwrite($this->fh, 'ERR: Exception during onShutdown()'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/PhpDisruptor/ExceptionHandler/IgnoreExceptionHandler.php: -------------------------------------------------------------------------------- 1 | fh, 'INFO: Exception processing: ' . $sequence . ' ' . $event); 13 | } 14 | 15 | /** 16 | * @inheritdoc 17 | */ 18 | public function handleOnStartException(\Exception $ex) 19 | { 20 | fwrite($this->fh, 'INFO: Exception during onStart()'); 21 | } 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | public function handleOnShutdownException(\Exception $ex) 27 | { 28 | fwrite($this->fh, 'INFO: Exception during onShutdown()'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/PhpDisruptor/FixedSequenceGroup.php: -------------------------------------------------------------------------------- 1 | sequences = $sequences; 30 | } 31 | 32 | /** 33 | * Get the minimum sequence value for the group. 34 | * 35 | * @return int the minimum sequence value for the group. 36 | */ 37 | public function get() 38 | { 39 | return Util::getMinimumSequence($this->sequences); 40 | } 41 | 42 | /** 43 | * Not supported. 44 | * 45 | * @throws Exception\UnsupportedMethodCallException 46 | */ 47 | public function set($value) 48 | { 49 | throw new Exception\UnsupportedMethodCallException('not supported'); 50 | } 51 | 52 | /** 53 | * Not supported. 54 | * 55 | * @throws Exception\UnsupportedMethodCallException 56 | */ 57 | public function compareAndSwap($oldValue, $newValue) 58 | { 59 | throw new Exception\UnsupportedMethodCallException('not supported'); 60 | } 61 | 62 | /** 63 | * Not supported. 64 | * 65 | * @throws Exception\UnsupportedMethodCallException 66 | */ 67 | public function incrementAndGet() 68 | { 69 | throw new Exception\UnsupportedMethodCallException('not supported'); 70 | } 71 | 72 | /** 73 | * Not supported. 74 | * 75 | * @throws Exception\UnsupportedMethodCallException 76 | */ 77 | public function addAndGet($increment) 78 | { 79 | throw new Exception\UnsupportedMethodCallException('not supported'); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/PhpDisruptor/LifecycleAwareInterface.php: -------------------------------------------------------------------------------- 1 | add($entities); 22 | } else if (is_array($entities) || $entities instanceof Traversable) { 23 | foreach ($entities as $entity) { 24 | $this->add($entity); 25 | } 26 | } else if (null !== $entities) { 27 | throw new Exception\InvalidArgumentException(sprintf( 28 | 'Parameter provided to %s must be an %s, %s or %s', 29 | __METHOD__, 'array', 'Traversable', 'PhpDisruptor\EventHandlerInterface' 30 | )); 31 | } 32 | } 33 | 34 | /** 35 | * @param EventHandlerInterface $entity 36 | */ 37 | public function add(EventHandlerInterface $entity) 38 | { 39 | $this[] = $entity; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Lists/EventProcessorList.php: -------------------------------------------------------------------------------- 1 | add($entities); 22 | } else if (is_array($entities) || $entities instanceof Traversable) { 23 | foreach ($entities as $entity) { 24 | $this->add($entity); 25 | } 26 | } else if (null !== $entities) { 27 | throw new Exception\InvalidArgumentException(sprintf( 28 | 'Parameter provided to %s must be an %s, %s or %s', 29 | __METHOD__, 'array', 'Traversable', 'PhpDisruptor\EventProcessor\AbstractEventProcessor' 30 | )); 31 | } 32 | } 33 | 34 | /** 35 | * @param AbstractEventProcessor $entity 36 | */ 37 | public function add(AbstractEventProcessor $entity) 38 | { 39 | $this[] = $entity; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Lists/EventTranslatorList.php: -------------------------------------------------------------------------------- 1 | add($entities); 22 | } else if (is_array($entities) || $entities instanceof Traversable) { 23 | foreach ($entities as $entity) { 24 | $this->add($entity); 25 | } 26 | } else if (null !== $entities) { 27 | throw new Exception\InvalidArgumentException(sprintf( 28 | 'Parameter provided to %s must be an %s, %s or %s', 29 | __METHOD__, 'array', 'Traversable', 'PhpDisruptor\EventTranslatorInterface' 30 | )); 31 | } 32 | } 33 | 34 | /** 35 | * @param EventTranslatorInterface $entity 36 | */ 37 | public function add(EventTranslatorInterface $entity) 38 | { 39 | $this[] = $entity; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Lists/SequenceList.php: -------------------------------------------------------------------------------- 1 | add($entities); 22 | } else if (is_array($entities) || $entities instanceof Traversable) { 23 | foreach ($entities as $entity) { 24 | $this->add($entity); 25 | } 26 | } else if (null !== $entities) { 27 | throw new Exception\InvalidArgumentException(sprintf( 28 | 'Parameter provided to %s must be an %s, %s or %s', 29 | __METHOD__, 'array', 'Traversable', 'PhpDisruptor\Sequence' 30 | )); 31 | } 32 | } 33 | 34 | /** 35 | * @param Sequence $entity 36 | */ 37 | public function add(Sequence $entity) 38 | { 39 | $this[] = $entity; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Lists/WorkHandlerList.php: -------------------------------------------------------------------------------- 1 | add($entities); 22 | } else if (is_array($entities) || $entities instanceof Traversable) { 23 | foreach ($entities as $entity) { 24 | $this->add($entity); 25 | } 26 | } else if (null !== $entities) { 27 | throw new Exception\InvalidArgumentException(sprintf( 28 | 'Parameter provided to %s must be an %s, %s or %s', 29 | __METHOD__, 'array', 'Traversable', 'PhpDisruptor\WorkHandlerInterface' 30 | )); 31 | } 32 | } 33 | 34 | /** 35 | * @param WorkHandlerInterface $entity 36 | */ 37 | public function add(WorkHandlerInterface $entity) 38 | { 39 | $this[] = $entity; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/PhpDisruptor/MultiProducerSequencer.php: -------------------------------------------------------------------------------- 1 | gatingSequenceCache = new Sequence(); 41 | 42 | $this->indexMask = $bufferSize - 1; 43 | $this->indexShift = Util::log2($bufferSize); 44 | 45 | $this->availableBuffer = new Threaded(); 46 | for ($i = 0; $i < $bufferSize; $i++) { 47 | $this->availableBuffer[$i] = -1; 48 | } 49 | } 50 | 51 | /** 52 | * @param int $requiredCapacity 53 | * @return bool 54 | * @throws Exception\InvalidArgumentException 55 | */ 56 | public function hasAvailableCapacity($requiredCapacity) 57 | { 58 | return $this->_internalHasAvailableCapacity($this->getSequences(), $requiredCapacity, $this->cursor->get()); 59 | } 60 | 61 | /** 62 | * @param SequenceList $gatingSequences 63 | * @param int $requiredCapacity 64 | * @param int $cursorValue 65 | * @return bool 66 | */ 67 | public function _internalHasAvailableCapacity(SequenceList $gatingSequences, $requiredCapacity, $cursorValue) // private !! only public for pthreads reasons 68 | { 69 | $wrapPoint = ($cursorValue + $requiredCapacity) - $this->bufferSize; 70 | $cachedGatingSequence = $this->gatingSequenceCache->get(); 71 | 72 | if ($wrapPoint > $cachedGatingSequence || $cachedGatingSequence > $cursorValue) { 73 | $minSequence = Util::getMinimumSequence($gatingSequences, $cursorValue); 74 | $this->gatingSequenceCache->set($minSequence); 75 | if ($wrapPoint > $minSequence) { 76 | return false; 77 | } 78 | } 79 | return true; 80 | } 81 | 82 | /** 83 | * @inheritdoc 84 | */ 85 | public function claim($sequence) 86 | { 87 | $this->cursor->set($sequence); 88 | } 89 | 90 | /** 91 | * @inheritdoc 92 | */ 93 | public function next($n = 1) 94 | { 95 | if ($n < 1) { 96 | throw new Exception\InvalidArgumentException('$n must be > 0'); 97 | } 98 | 99 | do { 100 | $current = $this->cursor->get(); 101 | $next = $current + $n; 102 | 103 | $wrapPoint = $next - $this->bufferSize; 104 | $cachedGatingSequence = $this->gatingSequenceCache->get(); 105 | 106 | if ($wrapPoint > $cachedGatingSequence || $cachedGatingSequence > $current) { 107 | $gatingSequence = Util::getMinimumSequence($this->getSequences(), $current); 108 | 109 | if ($wrapPoint > $gatingSequence) { 110 | $this->wait(1); 111 | continue; 112 | } 113 | 114 | $this->gatingSequenceCache->set($gatingSequence); 115 | } elseif ($this->cursor->compareAndSwap($current, $next)) { 116 | break; 117 | } 118 | } while (true); 119 | 120 | return $next; 121 | } 122 | 123 | /** 124 | * @inheritdoc 125 | */ 126 | public function tryNext($n = 1) 127 | { 128 | if ($n < 1) { 129 | throw new Exception\InvalidArgumentException('$n must be > 0'); 130 | } 131 | 132 | do { 133 | $current = $this->cursor->get(); 134 | $next = $current + $n; 135 | 136 | if (!$this->_internalHasAvailableCapacity($this->getSequences(), $n, $current)) { 137 | throw new Exception\InsufficientCapacityException('insufficient capacity'); 138 | } 139 | } while (!$this->cursor->compareAndSwap($current, $next)); 140 | 141 | return $next; 142 | } 143 | 144 | /** 145 | * @inheritdoc 146 | */ 147 | public function remainingCapacity() 148 | { 149 | $consumed = Util::getMinimumSequence($this->getSequences(), $this->cursor->get()); 150 | $produced = $this->cursor->get(); 151 | return $this->getBufferSize() - ($produced - $consumed); 152 | } 153 | 154 | /** 155 | * @inheritdoc 156 | */ 157 | public function publish($low, $high = null) 158 | { 159 | if (null === $high) { 160 | $this->_setAvailable($low); 161 | } else { 162 | for ($l = $low; $l <= $high; $l++) { 163 | $this->_setAvailable($l); 164 | } 165 | } 166 | $this->waitStrategy->signalAllWhenBlocking(); 167 | } 168 | 169 | /** 170 | * The below methods work on the availableBuffer flag. 171 | * 172 | * The prime reason is to avoid a shared sequence object between publisher threads. 173 | * (Keeping single pointers tracking start and end would require coordination 174 | * between the threads). 175 | * 176 | * -- Firstly we have the constraint that the delta between the cursor and minimum 177 | * gating sequence will never be larger than the buffer size (the code in 178 | * next/tryNext in the Sequence takes care of that). 179 | * -- Given that; take the sequence value and mask off the lower portion of the 180 | * sequence as the index into the buffer (indexMask). (aka modulo operator) 181 | * -- The upper portion of the sequence becomes the value to check for availability. 182 | * ie: it tells us how many times around the ring buffer we've been (aka division) 183 | * -- Because we can't wrap without the gating sequences moving forward (i.e. the 184 | * minimum gating sequence is effectively our last available position in the 185 | * buffer), when we have new data and successfully claimed a slot we can simply 186 | * write over the top. 187 | * 188 | * @param int $sequence 189 | * @return void 190 | */ 191 | public function _setAvailable($sequence) // private !! only public for pthreads reasons 192 | { 193 | $this->_setAvailableBufferValue($this->_calculateIndex($sequence), $this->_calculateAvailabilityFlag($sequence)); 194 | } 195 | 196 | /** 197 | * @param int $index 198 | * @param int $flag 199 | * @return void 200 | */ 201 | public function _setAvailableBufferValue($index, $flag) // private !! only public for pthreads reasons 202 | { 203 | do { 204 | $oldAvailableBuffer = $this->availableBuffer; 205 | $newAvailableBuffer = new Threaded(); 206 | $newAvailableBuffer->merge($oldAvailableBuffer); 207 | $newAvailableBuffer[$index] = $flag; 208 | } while (!$this->casMember('availableBuffer', $oldAvailableBuffer, $newAvailableBuffer)); 209 | } 210 | 211 | /** 212 | * @inheritdoc 213 | */ 214 | public function isAvailable($sequence) 215 | { 216 | $index = $this->_calculateIndex($sequence); 217 | $flag = $this->_calculateAvailabilityFlag($sequence); 218 | 219 | return $this->availableBuffer[$index] == $flag; 220 | } 221 | 222 | /** 223 | * @param int $lowerBound 224 | * @param int $availableSequence 225 | * @return int 226 | */ 227 | public function getHighestPublishedSequence($lowerBound, $availableSequence) 228 | { 229 | for ($sequence = $lowerBound; $sequence <= $availableSequence; $sequence++) { 230 | if (!$this->isAvailable($sequence)) { 231 | return $sequence - 1 ; 232 | } 233 | } 234 | return $availableSequence; 235 | } 236 | 237 | /** 238 | * @param int $sequence 239 | * @return int 240 | */ 241 | public function _calculateAvailabilityFlag($sequence) // private !! only public for pthreads reasons 242 | { 243 | return (int) ($sequence >> $this->indexShift); 244 | } 245 | 246 | /** 247 | * @param int $sequence 248 | * @return int 249 | */ 250 | public function _calculateIndex($sequence) // private !! only public for pthreads reasons 251 | { 252 | return (int) ($sequence & $this->indexMask); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/PhpDisruptor/ProcessingSequenceBarrier.php: -------------------------------------------------------------------------------- 1 | sequencer = $sequencer; 60 | $this->waitStrategy = $waitStrategy; 61 | $this->cursorSequence = $cursorSequence; 62 | $this->alerted = false; 63 | 64 | if (0 == count($dependentSequences)) { 65 | $this->dependentSequence = $cursorSequence; 66 | } else { 67 | $this->dependentSequence = new FixedSequenceGroup($dependentSequences); 68 | } 69 | } 70 | 71 | /** 72 | * Wait for the given sequence to be available for consumption. 73 | * 74 | * @param int $sequence to wait for 75 | * @return int the sequence up to which is available 76 | * @throws Exception\AlertException if a status change has occurred for the Disruptor 77 | * @throws Exception\InterruptedException if the thread needs awaking on a condition variable. 78 | * @throws Exception\TimeoutException 79 | */ 80 | public function waitFor($sequence) 81 | { 82 | $this->checkAlert(); 83 | 84 | $availableSequence = $this->waitStrategy->waitFor( 85 | $sequence, 86 | $this->cursorSequence, 87 | $this->dependentSequence, 88 | $this 89 | ); 90 | 91 | if ($availableSequence < $sequence) { 92 | return $availableSequence; 93 | } 94 | 95 | return $this->sequencer->getHighestPublishedSequence($sequence, $availableSequence); 96 | } 97 | 98 | /** 99 | * Get the current cursor value that can be read. 100 | * 101 | * @return int value of the cursor for entries that have been published. 102 | */ 103 | public function getCursor() 104 | { 105 | return $this->dependentSequence->get(); 106 | } 107 | 108 | /** 109 | * The current alert status for the barrier. 110 | * 111 | * @return bool true if in alert otherwise false. 112 | */ 113 | public function isAlerted() 114 | { 115 | return $this->alerted; 116 | } 117 | 118 | /** 119 | * Alert the EventProcessors of a status change and stay in this status until cleared. 120 | * 121 | * @return void 122 | */ 123 | public function alert() 124 | { 125 | $this->alerted = true; 126 | $this->waitStrategy->signalAllWhenBlocking(); 127 | } 128 | 129 | /** 130 | * Clear the current alert status. 131 | * 132 | * @return void 133 | */ 134 | public function clearAlert() 135 | { 136 | $this->alerted = false; 137 | } 138 | 139 | /** 140 | * Check if an alert has been raised and throw an AlertException if it has. 141 | * 142 | * @return void 143 | * @throws Exception\AlertException if alert has been raised. 144 | */ 145 | public function checkAlert() 146 | { 147 | if (true === $this->alerted) { 148 | throw new Exception\AlertException('alert'); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/PhpDisruptor/Sequence.php: -------------------------------------------------------------------------------- 1 | set($initialValue); 28 | } 29 | 30 | /** 31 | * Perform a volatile read of this sequence's value. 32 | * 33 | * @return int The current value of the sequence. 34 | */ 35 | public function get() 36 | { 37 | return $this->value; 38 | } 39 | 40 | /** 41 | * Perform an ordered write of this sequence. The intent is 42 | * a Store/Store barrier between this write and any previous 43 | * store. 44 | * 45 | * @param int $value The new value for the sequence. 46 | * @return void 47 | */ 48 | public function set($value) 49 | { 50 | $this->value = $value; 51 | } 52 | 53 | /** 54 | * Perform a compare and swap operation on the sequence. 55 | * 56 | * @param int $oldValue The expected current value. 57 | * @param int $newValue The value to update to. 58 | * @return bool true if the operation succeeds, false otherwise. 59 | */ 60 | public function compareAndSwap($oldValue, $newValue) 61 | { 62 | return $this->casMember('value', $oldValue, $newValue); 63 | } 64 | 65 | /** 66 | * Atomically increment the sequence by one. 67 | * 68 | * @return int The value after the increment 69 | */ 70 | public function incrementAndGet() 71 | { 72 | return $this->addAndGet(1); 73 | } 74 | 75 | /** 76 | * Atomically add the supplied value. 77 | * 78 | * @param int $increment The value to add to the sequence. 79 | * @return int The value after the increment. 80 | * @throws Exception\InvalidArgumentException 81 | */ 82 | public function addAndGet($increment) 83 | { 84 | do { 85 | $currentValue = $this->get(); 86 | $newValue = $currentValue + $increment; 87 | } while (!$this->compareAndSwap($currentValue, $newValue)); 88 | 89 | return $newValue; 90 | } 91 | 92 | /** 93 | * @return string 94 | */ 95 | public function __toString() 96 | { 97 | return (string) $this->get(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/PhpDisruptor/SequenceAggregateInterface.php: -------------------------------------------------------------------------------- 1 | value = self::INITIAL_VALUE; 19 | $this->sequences = new SequenceList(); 20 | } 21 | 22 | /** 23 | * Get the sequences 24 | * 25 | * @return Sequence[] 26 | */ 27 | public function getSequences() 28 | { 29 | return $this->sequences; 30 | } 31 | 32 | /** 33 | * Get the minimum sequence value for the group. 34 | * 35 | * @return int the minimum sequence value for the group. 36 | */ 37 | public function get() 38 | { 39 | return Util::getMinimumSequence($this->sequences); 40 | } 41 | 42 | /** 43 | * Set all Sequences in the group to a given value. 44 | * 45 | * @param int $value to set the group of sequences to. 46 | * @return void 47 | */ 48 | public function set($value) 49 | { 50 | foreach ($this->sequences as $sequence) { 51 | $sequence->set($value); 52 | } 53 | } 54 | 55 | /** 56 | * Add a Sequence into this aggregate. This should only be used during 57 | * initialisation. Use {@link SequenceGroup#addWhileRunning(Cursored, Sequence)} 58 | * 59 | * @param Sequence $sequence to be added to the aggregate. 60 | * @return void 61 | */ 62 | public function add(Sequence $sequence) 63 | { 64 | $this->sequences[] = $sequence; 65 | } 66 | 67 | /** 68 | * Remove the first occurrence of the Sequence from this aggregate. 69 | * 70 | * @param Sequence $sequence to be removed from this aggregate. 71 | * @return bool true if the sequence was removed otherwise false. 72 | */ 73 | public function remove(Sequence $sequence) 74 | { 75 | return SequenceGroups::removeSequence($this, $sequence); 76 | } 77 | 78 | /** 79 | * Get the size of the group. 80 | * 81 | * @return int the size of the group. 82 | */ 83 | public function count() 84 | { 85 | return count($this->sequences); 86 | } 87 | 88 | /** 89 | * Adds a sequence to the sequence group after threads have started to publish to 90 | * the Disruptor. It will set the sequences to cursor value of the ringBuffer 91 | * just after adding them. This should prevent any nasty rewind/wrapping effects. 92 | * 93 | * @param CursoredInterface $cursored The data structure that the owner of this sequence group will 94 | * be pulling it's events from. 95 | * @param Sequence $sequence The sequence to add. 96 | * @return void 97 | */ 98 | public function addWhileRunning(CursoredInterface $cursored, Sequence $sequence) 99 | { 100 | $sequencesToAdd = new SequenceList(); 101 | $sequencesToAdd[] = $sequence; 102 | SequenceGroups::addSequences($this, $cursored, $sequencesToAdd); 103 | } 104 | 105 | /** 106 | * @inheritdoc 107 | */ 108 | public function casSequences(SequenceList $oldSequences, SequenceList $newSequences) 109 | { 110 | return Util::casSequences($this, $oldSequences, $newSequences); 111 | } 112 | 113 | /** 114 | * @param SequenceList $sequences 115 | * @return void 116 | */ 117 | public function setSequences(SequenceList $sequences) 118 | { 119 | $this->sequences = $sequences; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/PhpDisruptor/SequenceGroups.php: -------------------------------------------------------------------------------- 1 | getSequences(); 23 | $updatedSequences = new SequenceList(); 24 | $updatedSequences->merge($currentSequences); 25 | $cursorSequence = $cursor->getCursor(); 26 | 27 | foreach ($sequencesToAdd as $sequence) { 28 | $sequence->set($cursorSequence); 29 | $updatedSequences[] = $sequence; 30 | } 31 | 32 | } while (!$sequenceAggregate->casSequences($currentSequences, $updatedSequences)); 33 | 34 | $cursorSequence = $cursor->getCursor(); 35 | foreach ($sequencesToAdd as $sequence) { 36 | $sequence->set($cursorSequence); 37 | } 38 | } 39 | 40 | /** 41 | * @param SequenceAggregateInterface $sequenceAggregate 42 | * @param Sequence $sequence 43 | * @return bool 44 | */ 45 | public static function removeSequence(SequenceAggregateInterface $sequenceAggregate, Sequence $sequence) 46 | { 47 | do { 48 | $oldSequences = $sequenceAggregate->getSequences(); 49 | $numToRemove = self::countMatching($oldSequences, $sequence); 50 | if (0 == $numToRemove) { 51 | break; 52 | } 53 | 54 | $oldSize = count($oldSequences); 55 | 56 | $newSequences = new SequenceList(); 57 | for ($i = 0, $pos = 0; $i < $oldSize; $i++) { 58 | $testSequence = $oldSequences[$i]; 59 | if ($testSequence !== $sequence) { 60 | $newSequences[$pos++] = $testSequence; 61 | } 62 | } 63 | } while (!$sequenceAggregate->casSequences($oldSequences, $newSequences)); 64 | return $numToRemove != 0; 65 | } 66 | 67 | /** 68 | * @param SequenceList $sequences 69 | * @param Sequence $sequence 70 | * @return int 71 | */ 72 | public static function countMatching(SequenceList $sequences, Sequence $sequence) 73 | { 74 | $numToRemove = 0; 75 | foreach ($sequences as $sequenceToTest) { 76 | if ($sequenceToTest == $sequence) { 77 | $numToRemove++; 78 | } 79 | } 80 | return $numToRemove; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/PhpDisruptor/SequenceReportingEventHandlerInterface.php: -------------------------------------------------------------------------------- 1 | sequencer = $sequencer; 21 | } 22 | 23 | /** 24 | * @return int 25 | */ 26 | public function get() 27 | { 28 | return $this->sequencer->getCursor(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/PhpDisruptor/SequencerInterface.php: -------------------------------------------------------------------------------- 1 | requiredCapacity slots 38 | * available. 39 | * 40 | * @param int|null $n 41 | * @return int the claimed sequence value 42 | * @throws Exception\InsufficientCapacityException 43 | */ 44 | public function tryNext($n = null); 45 | 46 | /** 47 | * Get the remaining capacity for this sequencer. 48 | * 49 | * @return int The number of slots remaining. 50 | */ 51 | public function remainingCapacity(); 52 | 53 | /** 54 | * Claim a specific sequence. Only used if initialising the ring buffer to 55 | * a specific value. 56 | * 57 | * @param int $sequence The sequence to initialise too. 58 | * @return void 59 | */ 60 | public function claim($sequence); 61 | 62 | /** 63 | * Publishes a sequence. Call when the event has been filled. 64 | * 65 | * @param int $low first sequence number to publish 66 | * @param int|null $high last sequence number to publish (optional) 67 | * @return void 68 | */ 69 | public function publish($low, $high = null); 70 | 71 | /** 72 | * Confirms if a sequence is published and the event is available for use; non-blocking. 73 | * 74 | * @param int $sequence of the buffer to check 75 | * @return bool true if the sequence is available for use, false if not 76 | */ 77 | public function isAvailable($sequence); 78 | 79 | /** 80 | * Add the specified gating sequences to this instance of the Disruptor. They will 81 | * safely and atomically added to the list of gating sequences. 82 | * 83 | * @param SequenceList $gatingSequences The sequences to add. 84 | * @return void 85 | * @throws Exception\InvalidArgumentException 86 | */ 87 | public function addGatingSequences(SequenceList $gatingSequences); 88 | 89 | /** 90 | * Remove the specified sequence from this sequencer. 91 | * 92 | * @param Sequence $sequence to be removed. 93 | * @return bool true if this sequence was found, false otherwise. 94 | */ 95 | public function removeGatingSequence(Sequence $sequence); 96 | 97 | /** 98 | * Create a new SequenceBarrier to be used by an EventProcessor to track which messages 99 | * are available to be read from the ring buffer given a list of sequences to track. 100 | * 101 | * @see SequenceBarrierInterface 102 | * @param SequenceList $sequencesToTrack 103 | * @return SequenceBarrierInterface A sequence barrier that will track the specified sequences. 104 | */ 105 | public function newBarrier(SequenceList $sequencesToTrack = null); 106 | 107 | /** 108 | * Get the minimum sequence value from all of the gating sequences 109 | * added to this ringBuffer. 110 | * 111 | * @return int The minimum gating sequence or the cursor sequence if 112 | * no sequences have been added. 113 | */ 114 | public function getMinimumSequence(); 115 | 116 | /** 117 | * Get the highest sequence value from all the gatting sequences 118 | * added to this ringBuffer. 119 | * 120 | * @param int $sequence 121 | * @param int $availableSequence 122 | * @return int The highest gating sequence or the cursor sequence if 123 | * no sequences have been added. 124 | */ 125 | public function getHighestPublishedSequence($sequence, $availableSequence); 126 | } 127 | -------------------------------------------------------------------------------- /src/PhpDisruptor/SingleProducerSequencer.php: -------------------------------------------------------------------------------- 1 | nextValue = Sequence::INITIAL_VALUE; 33 | $this->cachedValue = Sequence::INITIAL_VALUE; 34 | } 35 | 36 | /** 37 | * @param int $requiredCapacity 38 | * @return bool 39 | */ 40 | public function hasAvailableCapacity($requiredCapacity) 41 | { 42 | $nextValue = $this->nextValue; 43 | 44 | $wrapPoint = ($nextValue + $requiredCapacity) - $this->bufferSize; 45 | $cachedGatingSequence = $this->cachedValue; 46 | 47 | if ($wrapPoint > $cachedGatingSequence || $cachedGatingSequence > $nextValue) { 48 | $minSequence = Util::getMinimumSequence($this->getSequences(), $nextValue); 49 | $this->cachedValue = $minSequence; 50 | 51 | if ($wrapPoint > $minSequence) { 52 | return false; 53 | } 54 | } 55 | 56 | return true; 57 | } 58 | 59 | /** 60 | * @param int $n 61 | * @return int 62 | * @throws Exception\InvalidArgumentException 63 | */ 64 | public function next($n = 1) 65 | { 66 | if ($n < 1) { 67 | throw new Exception\InvalidArgumentException('$n must be > 0'); 68 | } 69 | 70 | $nextValue = $this->nextValue; 71 | 72 | $nextSequence = $nextValue + $n; 73 | $wrapPoint = $nextSequence - $this->bufferSize; 74 | $cachedGatingSequence = $this->cachedValue; 75 | 76 | if ($wrapPoint > $cachedGatingSequence || $cachedGatingSequence > $nextValue) { 77 | 78 | while ($wrapPoint > ($minSequence = Util::getMinimumSequence($this->getSequences(), $nextValue))) { 79 | $this->wait(1); 80 | } 81 | $this->cachedValue = $minSequence; 82 | } 83 | 84 | $this->nextValue = $nextSequence; 85 | 86 | return $nextSequence; 87 | } 88 | 89 | /** 90 | * @param int $n 91 | * @return int 92 | * @throws Exception\InvalidArgumentException 93 | * @throws Exception\InsufficientCapacityException 94 | */ 95 | public function tryNext($n = 1) 96 | { 97 | if ($n < 1) { 98 | throw new Exception\InvalidArgumentException('$n must be > 0'); 99 | } 100 | 101 | if (!$this->hasAvailableCapacity($n)) { 102 | throw new Exception\InsufficientCapacityException('insufficient capacity'); 103 | } 104 | 105 | $nextSequence = $this->nextValue += $n; 106 | return $nextSequence; 107 | } 108 | 109 | /** 110 | * @return int 111 | */ 112 | public function remainingCapacity() 113 | { 114 | $nextValue = $this->nextValue; 115 | 116 | $consumed = Util::getMinimumSequence($this->getSequences(), $nextValue); 117 | $produced = $nextValue; 118 | 119 | return $this->getBufferSize() - ($produced - $consumed); 120 | } 121 | 122 | /** 123 | * @param int 124 | * @return void 125 | */ 126 | public function claim($sequence) 127 | { 128 | $this->nextValue = $sequence; 129 | } 130 | 131 | /** 132 | * @param int $low 133 | * @param int|null $high will get ignored in this implementation 134 | * @return void 135 | */ 136 | public function publish($low, $high = null) 137 | { 138 | $this->cursor->set($low); 139 | $this->waitStrategy->signalAllWhenBlocking(); 140 | } 141 | 142 | /** 143 | * @param int $sequence 144 | * @return bool 145 | */ 146 | public function isAvailable($sequence) 147 | { 148 | return $sequence <= $this->cursor->get(); 149 | } 150 | 151 | /** 152 | * @param int $lowerBound 153 | * @param int $availableSequence 154 | * @return int 155 | */ 156 | public function getHighestPublishedSequence($lowerBound, $availableSequence) 157 | { 158 | return $availableSequence; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/PhpDisruptor/TimeoutHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 17 | * From Hacker's Delight, Chapter 3, Harry S. Warren Jr. 18 | * 19 | * @param int $x Value to round up 20 | * @return int The next power of 2 from x inclusive 21 | * @throws Exception\InvalidArgumentException 22 | */ 23 | public static function ceilingNextPowerOfTwo($x) 24 | { 25 | $size = PHP_INT_SIZE * 8; 26 | $binary = str_pad(decbin($x -1), $size, 0, STR_PAD_LEFT); 27 | $numberOfLeadingZeros = strpos($binary, '1'); 28 | 29 | return 1 << ($size - $numberOfLeadingZeros); 30 | } 31 | 32 | /** 33 | * Get the minimum sequence from an SequenceList 34 | * 35 | * @param SequenceList $sequences to compare 36 | * @param int|null $minimum 37 | * @return int the minimum sequence found or PHP_INT_MAX if the sequence list is empty 38 | * @throws Exception\InvalidArgumentException 39 | */ 40 | public static function getMinimumSequence(SequenceList $sequences, $minimum = PHP_INT_MAX) 41 | { 42 | foreach ($sequences as $sequence) { 43 | $value = $sequence->get(); 44 | $minimum = min($minimum, $value); 45 | } 46 | return $minimum; 47 | } 48 | 49 | /** 50 | * Get a SequenceList of Sequences for the passed EventProcessors 51 | * 52 | * @param EventProcessorList $processors for which to get the sequences 53 | * @return SequenceList of Sequences 54 | */ 55 | public static function getSequencesFor(EventProcessorList $processors) 56 | { 57 | $sequences = new SequenceList(); 58 | foreach ($processors as $eventProcessor) { 59 | $sequences->add($eventProcessor->getSequence()); 60 | } 61 | return $sequences; 62 | } 63 | 64 | /** 65 | * @param SequenceAggregateInterface $sequenceAggregate 66 | * @param SequenceList $oldSequences 67 | * @param SequenceList $newSequences 68 | * @return bool 69 | */ 70 | public static function casSequences( 71 | SequenceAggregateInterface $sequenceAggregate, 72 | SequenceList $oldSequences, 73 | SequenceList $newSequences 74 | ) { 75 | $set = false; 76 | $sequenceAggregate->lock(); 77 | if ($sequenceAggregate->getSequences() == $oldSequences) { 78 | $sequenceAggregate->setSequences($newSequences); 79 | $set = true; 80 | } 81 | $sequenceAggregate->unlock(); 82 | return $set; 83 | } 84 | 85 | /** 86 | * Calculate the log base 2 of the supplied integer, essentially reports the location 87 | * of the highest bit. 88 | * 89 | * @param int $i Value to calculate log2 for. 90 | * @return int The log2 value 91 | * @throws Exception\InvalidArgumentException 92 | */ 93 | public static function log2($i) 94 | { 95 | $r = 0; 96 | while (($i >>= 1) != 0) { 97 | ++$r; 98 | } 99 | return $r; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/PhpDisruptor/WaitStrategy/BlockingWaitStrategy.php: -------------------------------------------------------------------------------- 1 | mutex = Mutex::create(false); 32 | $this->cond = Cond::create(); 33 | } 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | public function waitFor( 39 | $sequence, 40 | Sequence $cursor, 41 | Sequence $dependentSequence, 42 | SequenceBarrierInterface $barrier 43 | ) { 44 | if (($availableSequence = $cursor->get()) < $sequence) { 45 | Mutex::lock($this->mutex); 46 | try { 47 | while (($availableSequence = $cursor->get()) < $sequence) { 48 | $barrier->checkAlert(); 49 | Cond::wait($this->cond, $this->mutex); 50 | } 51 | } catch (\Exception $e) { 52 | Mutex::unlock($this->mutex); 53 | } 54 | Mutex::unlock($this->mutex); 55 | } 56 | 57 | while (($availableSequence = $cursor->get()) < $sequence) { 58 | $barrier->checkAlert(); 59 | } 60 | 61 | return $availableSequence; 62 | } 63 | 64 | /** 65 | * @inheritdoc 66 | */ 67 | public function signalAllWhenBlocking() 68 | { 69 | Mutex::lock($this->mutex); 70 | Cond::broadcast($this->cond); 71 | Mutex::unlock($this->mutex); 72 | } 73 | 74 | /** 75 | * Destroy the mutex 76 | */ 77 | public function __destruct() 78 | { 79 | Cond::destroy($this->cond); 80 | Mutex::destroy($this->mutex); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/PhpDisruptor/WaitStrategy/BusySpinWaitStrategy.php: -------------------------------------------------------------------------------- 1 | get()) < $sequence) { 34 | $barrier->checkAlert(); 35 | } 36 | return $availableSequence; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function signalAllWhenBlocking() 43 | { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/PhpDisruptor/WaitStrategy/SleepingWaitStrategy.php: -------------------------------------------------------------------------------- 1 | get()) < $sequence) { 31 | 32 | $barrier->checkAlert(); 33 | 34 | if ($counter > 100) { 35 | --$counter; 36 | } else if ($counter > 0) { 37 | --$counter; 38 | $this->wait(1); 39 | } else { 40 | time_nanosleep(0, 1); 41 | } 42 | } 43 | 44 | return $availableSequence; 45 | } 46 | 47 | public function signalAllWhenBlocking() 48 | { 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/PhpDisruptor/WaitStrategy/TimeoutBlockingWaitStrategy.php: -------------------------------------------------------------------------------- 1 | timeoutMicros = $timeUnit->toMicros($timeout); 39 | $this->mutex = Mutex::create(false); 40 | $this->cond = Cond::create(); 41 | } 42 | 43 | /** 44 | * Wait for the given sequence to be available. It is possible for this method to return a value 45 | * less than the sequence number supplied depending on the implementation of the WaitStrategy. A common 46 | * use for this is to signal a timeout. Any EventProcessor that is using a WaitStragegy to get notifications 47 | * about message becoming available should remember to handle this case. The BatchEventProcessor explicitly 48 | * handles this case and will signal a timeout if required. 49 | * 50 | * @param int $sequence to be waited on. 51 | * @param Sequence $cursor the main sequence from ringbuffer. Wait/notify strategies will 52 | * need this as it's the only sequence that is also notified upon update. 53 | * @param Sequence $dependentSequence on which to wait. 54 | * @param SequenceBarrierInterface $barrier the processor is waiting on. 55 | * @return int the sequence that is available which may be greater than the requested sequence. 56 | * @throws Exception\AlertException if the status of the Disruptor has changed. 57 | * @throws Exception\InterruptedException if the thread is interrupted. 58 | * @throws Exception\TimeoutException 59 | */ 60 | public function waitFor( 61 | $sequence, 62 | Sequence $cursor, 63 | Sequence $dependentSequence, 64 | SequenceBarrierInterface $barrier 65 | ) { 66 | $micros = $this->timeoutMicros; 67 | 68 | if (($availableSequence = $cursor->get()) < $sequence) { 69 | Mutex::lock($this->mutex); 70 | while (($availableSequence = $cursor->get()) < $sequence) { 71 | $barrier->checkAlert(); 72 | $s = microtime(); 73 | Cond::wait($this->cond, $this->mutex, $micros); 74 | $micros = (microtime() - $s) * 1000000 - $micros; 75 | if ($micros <= 0) { 76 | Mutex::unlock($this->mutex); 77 | throw new Exception\TimeoutException(); 78 | } 79 | } 80 | Mutex::unlock($this->mutex); 81 | } 82 | 83 | while (($availableSequence = $dependentSequence->get()) < $sequence) { 84 | $barrier->checkAlert(); 85 | } 86 | 87 | return $availableSequence; 88 | } 89 | 90 | /** 91 | * Implementations should signal the waiting EventProcessors that the cursor has advanced. 92 | * 93 | * @return void 94 | */ 95 | public function signalAllWhenBlocking() 96 | { 97 | Mutex::lock($this->mutex); 98 | Cond::broadcoast($this->cond); 99 | Mutex::unlock($this->mutex); 100 | } 101 | 102 | public function __destruct() 103 | { 104 | Cond::destroy($this->cond); 105 | Mutex::destroy($this->mutex); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/PhpDisruptor/WaitStrategy/WaitStrategyInterface.php: -------------------------------------------------------------------------------- 1 | get()) < $sequence) { 49 | 50 | $barrier->checkAlert(); 51 | 52 | if (0 == $counter) { 53 | $this->wait(1); 54 | } else { 55 | --$counter; 56 | } 57 | } 58 | 59 | return $availableSequence; 60 | } 61 | 62 | /** 63 | * Implementations should signal the waiting EventProcessors that the cursor has advanced. 64 | * 65 | * @return void 66 | */ 67 | public function signalAllWhenBlocking() 68 | { 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/PhpDisruptor/WorkHandlerInterface.php: -------------------------------------------------------------------------------- 1 | started = false; 57 | $this->workSequence = new Sequence(SequencerInterface::INITIAL_CURSOR_VALUE); 58 | $this->ringBuffer = $ringBuffer; 59 | $this->eventClass = $ringBuffer->getEventClass(); 60 | $this->workProcessors = new Threaded(); 61 | foreach ($workHandlers as $workHandler) { 62 | $this->workProcessors[] = new WorkProcessor( 63 | $ringBuffer, 64 | $sequenceBarrier, 65 | $workHandler, 66 | $exceptionHandler, 67 | $this->workSequence 68 | ); 69 | } 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getEventClass() 76 | { 77 | return $this->eventClass; 78 | } 79 | 80 | /** 81 | * Create worker pool from ring buffer 82 | * 83 | * @param RingBuffer $ringBuffer 84 | * @param SequenceBarrierInterface $sequenceBarrier 85 | * @param ExceptionHandlerInterface $exceptionHandler 86 | * @param WorkHandlerList $workHandlers 87 | * @return WorkerPool 88 | */ 89 | public static function createFromRingBuffer( 90 | RingBuffer $ringBuffer, 91 | SequenceBarrierInterface $sequenceBarrier, 92 | ExceptionHandlerInterface $exceptionHandler, 93 | WorkHandlerList $workHandlers 94 | ) { 95 | return new self($ringBuffer, $sequenceBarrier, $exceptionHandler, $workHandlers); 96 | } 97 | 98 | /** 99 | * Constructor 100 | * 101 | * @param EventFactoryInterface $eventFactory 102 | * @param ExceptionHandlerInterface $exceptionHandler 103 | * @param WorkHandlerList $workHandlers 104 | * @return WorkerPool 105 | */ 106 | public static function createFromEventFactory( 107 | EventFactoryInterface $eventFactory, 108 | ExceptionHandlerInterface $exceptionHandler, 109 | WorkHandlerList $workHandlers 110 | ) { 111 | $ringBuffer = RingBuffer::createMultiProducer($eventFactory, 1024); 112 | $sequenceBarrier = $ringBuffer->newBarrier(); 113 | 114 | $workerPool = new self($ringBuffer, $sequenceBarrier, $exceptionHandler, $workHandlers); 115 | 116 | $ringBuffer->addGatingSequences($workerPool->getWorkerSequences()); 117 | 118 | return $workerPool; 119 | } 120 | 121 | /** 122 | * @return Sequence[] 123 | */ 124 | public function getWorkerSequences() 125 | { 126 | $sequences = new SequenceList(); 127 | foreach ($this->workProcessors as $workProcessor) { 128 | $sequences[] = $workProcessor->getSequence(); 129 | } 130 | return $sequences; 131 | } 132 | 133 | /** 134 | * Start the worker pool processing events in sequence 135 | * 136 | * @return RingBuffer 137 | * @throws Exception\InvalidArgumentException 138 | */ 139 | public function start() 140 | { 141 | if (!$this->casMember('started', false, true)) { 142 | throw new Exception\InvalidArgumentException( 143 | 'WorkerPool has already been started and cannot be restarted until halted' 144 | ); 145 | } 146 | 147 | $cursor = $this->ringBuffer->getCursor(); 148 | $this->workSequence->set($cursor); 149 | 150 | foreach ($this->workProcessors as $workProcessor) { 151 | $workProcessor->getSequence()->set($cursor); 152 | $workProcessor-start(); 153 | } 154 | 155 | return $this->ringBuffer; 156 | } 157 | 158 | /** 159 | * Wait for the RingBuffer to drain of published events then halt the workers 160 | * 161 | * @return void 162 | */ 163 | public function drainAndHalt() 164 | { 165 | $workerSequences = $this->getWorkerSequences(); 166 | 167 | while ($this->ringBuffer->getCursor() > Util::getMinimumSequence($workerSequences)) { 168 | $this->wait(1); 169 | } 170 | 171 | $this->halt(); 172 | } 173 | 174 | /** 175 | * Halt all workers immediately at the end of their current cycle 176 | * 177 | * @return void 178 | */ 179 | public function halt() 180 | { 181 | foreach ($this->workProcessors as $workProcessor) { 182 | $workProcessor->halt(); 183 | } 184 | $this->started = false; 185 | } 186 | 187 | /** 188 | * @return bool 189 | */ 190 | public function isRunning() 191 | { 192 | return $this->started; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /tests/Bootstrap.php: -------------------------------------------------------------------------------- 1 | add('PhpDisruptorTest\\', __DIR__); 37 | $loader->add('\\', __DIR__); 38 | 39 | unset($files, $file, $loader); 40 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/AggregateEventHandler/AggregateEventHandlerTest.php: -------------------------------------------------------------------------------- 1 | result = $result = new ResultCounter(); 36 | $this->eventHandlerOne = new EventHandler('stdClass', $result); 37 | $this->eventHandlerTwo = new EventHandler('stdClass', $result); 38 | $this->eventHandlerThree = new EventHandler('stdClass', $result); 39 | } 40 | 41 | public function testShouldCallOnEventInSequence() 42 | { 43 | $event = new \stdClass(); 44 | $sequence = 3; 45 | $endOfBatch = true; 46 | 47 | $aggregateEventHandler = $this->prepareAggregateEventHandler(); 48 | 49 | $aggregateEventHandler->onEvent($event, $sequence, $endOfBatch); 50 | 51 | $this->assertEquals('123', $this->result->getResult()); 52 | } 53 | 54 | public function testShouldCallOnStartInSequence() 55 | { 56 | $aggregateEventHandler = $this->prepareAggregateEventHandler(); 57 | 58 | $aggregateEventHandler->onShutdown(); 59 | 60 | $this->assertEquals('123', $this->result->getResult()); 61 | } 62 | 63 | public function testShouldHandleEmptyListOfEventHandlers() 64 | { 65 | $handlers = new EventHandlerList(); 66 | $aggregateEventHandler = new AggregateEventHandler('stdClass', $handlers); 67 | $event = new \stdClass(); 68 | $aggregateEventHandler->onEvent($event, 0, true); 69 | $aggregateEventHandler->onStart(); 70 | $aggregateEventHandler->onShutdown(); 71 | } 72 | 73 | private function prepareAggregateEventHandler() 74 | { 75 | $handlers = new EventHandlerList(array( 76 | $this->eventHandlerOne, 77 | $this->eventHandlerTwo, 78 | $this->eventHandlerThree 79 | )); 80 | 81 | $aggregateEventHandler = new AggregateEventHandler( 82 | 'stdClass', 83 | $handlers 84 | ); 85 | return $aggregateEventHandler; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/AggregateEventHandler/TestAsset/EventHandler.php: -------------------------------------------------------------------------------- 1 | eventClass = $eventClass; 31 | $this->result = $result; 32 | } 33 | 34 | /** 35 | * Return the used event class name 36 | * 37 | * @return string 38 | */ 39 | public function getEventClass() 40 | { 41 | return $this->eventClass; 42 | } 43 | 44 | /** 45 | * Called when a publisher has published an event to the RingBuffer 46 | * 47 | * @param object $event published to the RingBuffer 48 | * @param int $sequence of the event being processed 49 | * @param bool $endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer 50 | * @return void 51 | * @throws \Exception if the EventHandler would like the exception handled further up the chain. 52 | */ 53 | public function onEvent($event, $sequence, $endOfBatch) 54 | { 55 | $this->result->appendToResult(); 56 | } 57 | 58 | /** 59 | * Called once on thread start before first event is available. 60 | * 61 | * @return void 62 | */ 63 | public function onStart() 64 | { 65 | $this->result->appendToResult(); 66 | } 67 | 68 | /** 69 | * Called once just before the thread is shutdown. 70 | * 71 | * Sequence event processing will already have stopped before this method is called. No events will 72 | * be processed after this message. 73 | * 74 | * @return void 75 | */ 76 | public function onShutdown() 77 | { 78 | $this->result->appendToResult(); 79 | } 80 | 81 | 82 | /** 83 | * @return array 84 | */ 85 | public function getResult() 86 | { 87 | return $this->result; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/AggregateEventHandler/TestAsset/ResultCounter.php: -------------------------------------------------------------------------------- 1 | count = 0; 22 | } 23 | 24 | public function incrementAndGet() 25 | { 26 | return ++$this->count; 27 | } 28 | 29 | public function appendToResult() 30 | { 31 | $this->result .= $this->incrementAndGet(); 32 | } 33 | 34 | public function getResult() 35 | { 36 | return $this->result; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/Dsl/ConsumerRepository/ConsumerRepositoryTest.php: -------------------------------------------------------------------------------- 1 | eventFactory = new TestEventFactory(); 61 | $this->consumerRepository = new ConsumerRepository($this->eventFactory); 62 | 63 | $sequence1 = new Sequence(); 64 | $sequence2 = new Sequence(); 65 | $this->eventProcessor1 = new TestEventProcessor($sequence1); 66 | $this->eventProcessor2 = new TestEventProcessor($sequence2); 67 | 68 | $this->handler1 = new SleepingEventHandler($this->eventFactory->getEventClass()); 69 | $this->handler2 = new SleepingEventHandler($this->eventFactory->getEventClass()); 70 | 71 | $ringBuffer = RingBuffer::createMultiProducer($this->eventFactory, 64); 72 | 73 | $this->barrier1 = $ringBuffer->newBarrier(); 74 | $this->barrier2 = $ringBuffer->newBarrier(); 75 | } 76 | 77 | public function testShouldGetBarrierByHandler() 78 | { 79 | $this->consumerRepository->addEventProcessor($this->eventProcessor1, $this->handler1, $this->barrier1); 80 | $this->assertSame($this->barrier1, $this->consumerRepository->getBarrierFor($this->handler1)); 81 | } 82 | 83 | public function testShouldReturnNullForBarrierWhenHandlerIsNotRegistered() 84 | { 85 | $this->assertNull($this->consumerRepository->getBarrierFor($this->handler1)); 86 | } 87 | 88 | public function testShouldGetLastEventProcessorsInChain() 89 | { 90 | $this->consumerRepository->addEventProcessor($this->eventProcessor1, $this->handler1, $this->barrier1); 91 | $this->consumerRepository->addEventProcessor($this->eventProcessor2, $this->handler2, $this->barrier2); 92 | 93 | $sequences = new SequenceList(); 94 | $sequences[] = $this->eventProcessor2->getSequence(); 95 | $this->consumerRepository->unMarkEventProcessorsAsEndOfChain($sequences); 96 | 97 | $lastEventProcessorsInChain = $this->consumerRepository->getLastSequenceInChain(true); 98 | $this->assertEquals(1, count($lastEventProcessorsInChain)); 99 | $this->assertTrue($this->eventProcessor1->getSequence() === $lastEventProcessorsInChain[0]); 100 | } 101 | 102 | public function testShouldRetrieveEventProcessorForHandler() 103 | { 104 | $this->consumerRepository->addEventProcessor($this->eventProcessor1, $this->handler1, $this->barrier1); 105 | $this->assertTrue($this->consumerRepository->getEventProcessorFor($this->handler1) === $this->eventProcessor1); 106 | } 107 | 108 | /** 109 | * @expectedException PhpDisruptor\Exception\InvalidArgumentException 110 | * @expectedExceptionMessage The given event handler is not processing events 111 | */ 112 | public function testShouldThrowExceptionWhenHandlerIsNotRegistered() 113 | { 114 | $eventFactory = new TestEventFactory(); 115 | $eventHandler = new SleepingEventHandler($eventFactory->getEventClass()); 116 | $this->consumerRepository->getEventProcessorFor($eventHandler); 117 | } 118 | 119 | public function testShouldIterateAllEventProcessors() 120 | { 121 | $this->consumerRepository->addEventProcessor($this->eventProcessor1, $this->handler1, $this->barrier1); 122 | $this->consumerRepository->addEventProcessor($this->eventProcessor2, $this->handler2, $this->barrier2); 123 | 124 | $seen1 = false; 125 | $seen2 = false; 126 | 127 | foreach($this->consumerRepository->getConsumerInfos() as $info) { 128 | if (!$seen1 129 | && $info->getEventProcessor() === $this->eventProcessor1 130 | && $info->getHandler() === $this->handler1 131 | ) { 132 | $seen1 = true; 133 | } else if (!$seen2 134 | && $info->getEventProcessor() === $this->eventProcessor2 135 | && $info->getHandler() === $this->handler2 136 | ) { 137 | $seen2 = true; 138 | } else { 139 | $this->fail('Unexpected event processor info'); 140 | } 141 | } 142 | 143 | $this->assertTrue($seen1); 144 | $this->assertTrue($seen2); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/Dsl/Disruptor/DisruptorTest.php: -------------------------------------------------------------------------------- 1 | eventClass = $eventFactory->getEventClass(); 52 | $producerType = ProducerType::SINGLE(); 53 | $this->disruptor = new Disruptor($eventFactory, 4, $producerType); 54 | $this->delayedEventHandlers = new EventHandlerList(); 55 | $this->testWorkHandlers = new WorkHandlerList(); 56 | } 57 | 58 | protected function tearDown() 59 | { 60 | foreach ($this->delayedEventHandlers as $delayedEventHandler) { 61 | $delayedEventHandler->stopWaiting(); 62 | } 63 | 64 | foreach ($this->testWorkHandlers as $testWorkHandler) { 65 | $testWorkHandler->stopWaiting(); 66 | } 67 | 68 | $this->disruptor->halt(); 69 | } 70 | 71 | public function testShouldCreateEventProcessorGroupForFirstEventProcessors() 72 | { 73 | $eventHandler1 = new SleepingEventHandler($this->eventClass); 74 | $eventHandler2 = new SleepingEventHandler($this->eventClass); 75 | 76 | $eventHandlerList = new EventHandlerList(array($eventHandler1, $eventHandler2)); 77 | 78 | $eventHandlerGroup = $this->disruptor->handleEventsWithEventHandlers($eventHandlerList); 79 | $this->disruptor->start(); 80 | 81 | $this->assertNotNull($eventHandlerGroup); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/Dsl/Disruptor/TestAsset/DelayedEventHandler.php: -------------------------------------------------------------------------------- 1 | readyToProcessEvent = false; 33 | $this->stopped = false; 34 | if (null === $barrier) { 35 | $barrier = new CyclicBarrier(2); 36 | } 37 | $this->cyclicBarrier = $barrier; 38 | } 39 | 40 | /** 41 | * Return the used event class name 42 | * 43 | * @return string 44 | */ 45 | public function getEventClass() 46 | { 47 | return 'PhpDisruptorTest\TestAsset\TestEvent'; 48 | } 49 | 50 | /** 51 | * Called when a publisher has published an event to the RingBuffer 52 | * 53 | * @param object $event published to the RingBuffer 54 | * @param int $sequence of the event being processed 55 | * @param bool $endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer 56 | * @return void 57 | * @throws \Exception if the EventHandler would like the exception handled further up the chain. 58 | */ 59 | public function onEvent($event, $sequence, $endOfBatch) 60 | { 61 | $this->waitForAndSetFlag(false); 62 | } 63 | 64 | public function processEvent() 65 | { 66 | $this->waitForAndSetFlag(true); 67 | } 68 | 69 | public function stopWaiting() 70 | { 71 | $this->stopped = true; 72 | } 73 | 74 | public function waitForAndSetFlag($bool) 75 | { 76 | while(!$this->casMember('readyToProcessEvent', !$bool, $bool)) { 77 | $this->wait(1); 78 | } 79 | } 80 | 81 | public function onStart() 82 | { 83 | try 84 | { 85 | $this->cyclicBarrier->await(); 86 | } catch (BrokenBarrierException $e) { 87 | throw new \RuntimeException($e); 88 | } 89 | } 90 | 91 | public function onShutdown() 92 | { 93 | } 94 | 95 | public function awaitStart() 96 | { 97 | $this->cyclicBarrier->await(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/Dsl/Disruptor/TestAsset/TestWorkHandler.php: -------------------------------------------------------------------------------- 1 | stopped = false; 21 | $this->readyToProcessEvent = false; 22 | } 23 | 24 | /** 25 | * Return the used event class name 26 | * 27 | * @return string 28 | */ 29 | public function getEventClass() 30 | { 31 | return 'PhpDisruptorTest\TestAsset\TestEvent'; 32 | } 33 | 34 | /** 35 | * @param object $event 36 | * @return void 37 | * @throws Exception 38 | */ 39 | public function onEvent($event) 40 | { 41 | $this->waitForAndSetFlag(false); 42 | } 43 | 44 | public function processEvent($event) 45 | { 46 | $this->waitForAndSetFlag(true); 47 | } 48 | 49 | public function stopWaiting() 50 | { 51 | $this->stopped = true; 52 | } 53 | 54 | public function waitForAndSetFlag($bool) 55 | { 56 | while (!$this->casMember('readyToProcessEvent', !$bool, $bool)) { 57 | $this->wait(1); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/EventProcessor/BatchEventProcessor/BatchEventProcessorTest.php: -------------------------------------------------------------------------------- 1 | ringBuffer = RingBuffer::createMultiProducer($factory, 16); 36 | $this->sequenceBarrier = $this->ringBuffer->newBarrier(); 37 | $this->latch = new CountDownLatch(1); 38 | } 39 | 40 | public function testShouldCallMethodsInLifecycleOrder() 41 | { 42 | $eventHandler = new EventHandler('PhpDisruptorTest\TestAsset\StubEvent', $this->latch); 43 | $batchEventProcessor = new BatchEventProcessor( 44 | 'PhpDisruptorTest\TestAsset\StubEvent', 45 | $this->ringBuffer, 46 | $this->sequenceBarrier, 47 | $eventHandler 48 | ); 49 | 50 | $batchEventProcessor->start(); 51 | 52 | $this->assertEquals(-1, $batchEventProcessor->getSequence()->get()); 53 | $this->ringBuffer->publish($this->ringBuffer->next()); 54 | 55 | $this->latch->await(); 56 | 57 | $batchEventProcessor->halt(); 58 | $batchEventProcessor->join(); 59 | 60 | $expectedResult = new Threaded(); 61 | $expectedResult[] = 'onStart'; 62 | $expectedResult[] = 'PhpDisruptorTest\TestAsset\StubEvent-0-1'; 63 | $expectedResult[] = 'onShutdown'; 64 | 65 | $this->assertEquals($expectedResult, $eventHandler->getResult()); 66 | } 67 | 68 | public function testShouldCallMethodsInLifecycleOrderForBatch() 69 | { 70 | $eventHandler = new EventHandler('PhpDisruptorTest\TestAsset\StubEvent', $this->latch); 71 | $batchEventProcessor = new BatchEventProcessor( 72 | 'PhpDisruptorTest\TestAsset\StubEvent', 73 | $this->ringBuffer, 74 | $this->sequenceBarrier, 75 | $eventHandler 76 | ); 77 | 78 | $this->ringBuffer->publish($this->ringBuffer->next()); 79 | $this->ringBuffer->publish($this->ringBuffer->next()); 80 | $this->ringBuffer->publish($this->ringBuffer->next()); 81 | 82 | $batchEventProcessor->start(); 83 | 84 | $this->latch->await(); 85 | 86 | $batchEventProcessor->halt(); 87 | $batchEventProcessor->join(); 88 | 89 | $expectedResult = new Threaded(); 90 | $expectedResult[] = 'onStart'; 91 | $expectedResult[] = 'PhpDisruptorTest\TestAsset\StubEvent-0-0'; 92 | $expectedResult[] = 'PhpDisruptorTest\TestAsset\StubEvent-1-0'; 93 | $expectedResult[] = 'PhpDisruptorTest\TestAsset\StubEvent-2-1'; 94 | $expectedResult[] = 'onShutdown'; 95 | 96 | $this->assertEquals($expectedResult, $eventHandler->getResult()); 97 | } 98 | 99 | public function testShouldCallExceptionHandlerOnUncaughtException() 100 | { 101 | $exceptionHandler = new TestExceptionHandler(); 102 | $eventHandler = new ExEventHandler('PhpDisruptorTest\TestAsset\StubEvent', $this->latch); 103 | $batchEventProcessor = new BatchEventProcessor( 104 | 'PhpDisruptorTest\TestAsset\StubEvent', 105 | $this->ringBuffer, 106 | $this->sequenceBarrier, 107 | $eventHandler 108 | ); 109 | $batchEventProcessor->setExceptionHandler($exceptionHandler); 110 | 111 | $batchEventProcessor->start(); 112 | 113 | $this->ringBuffer->publish($this->ringBuffer->next()); 114 | 115 | $this->latch->await(); 116 | 117 | $batchEventProcessor->halt(); 118 | $batchEventProcessor->join(); 119 | 120 | $expectedResult = new Threaded(); 121 | $expectedResult[] = 'onStart'; 122 | $expectedResult[] = 'onShutdown'; 123 | 124 | $this->assertEquals($expectedResult, $eventHandler->getResult()); 125 | 126 | $expectedException = new Threaded(); 127 | $expectedException[] = 'PhpDisruptorTest\EventProcessor\BatchEventProcessor\TestAsset\TestExceptionHandler' 128 | . '::handleEventExceptionException-0-PhpDisruptorTest\TestAsset\StubEvent'; 129 | 130 | $this->assertEquals($expectedException, $exceptionHandler->getResult()); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/EventProcessor/BatchEventProcessor/TestAsset/EventHandler.php: -------------------------------------------------------------------------------- 1 | eventClass = $eventClass; 37 | $this->result = new Threaded(); 38 | $this->latch = $latch; 39 | } 40 | 41 | /** 42 | * Return the used event class name 43 | * 44 | * @return string 45 | */ 46 | public function getEventClass() 47 | { 48 | return $this->eventClass; 49 | } 50 | 51 | /** 52 | * Called when a publisher has published an event to the RingBuffer 53 | * 54 | * @param object $event published to the RingBuffer 55 | * @param int $sequence of the event being processed 56 | * @param bool $endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer 57 | * @return void 58 | * @throws \Exception if the EventHandler would like the exception handled further up the chain. 59 | */ 60 | public function onEvent($event, $sequence, $endOfBatch) 61 | { 62 | $this->result[] = get_class($event) . '-' . $sequence . '-' . (string) (int) $endOfBatch; 63 | $this->latch->countDown(); 64 | } 65 | 66 | /** 67 | * Called once on thread start before first event is available. 68 | * 69 | * @return void 70 | */ 71 | public function onStart() 72 | { 73 | $this->result[] = 'onStart'; 74 | } 75 | 76 | /** 77 | * Called once just before the thread is shutdown. 78 | * 79 | * Sequence event processing will already have stopped before this method is called. No events will 80 | * be processed after this message. 81 | * 82 | * @return void 83 | */ 84 | public function onShutdown() 85 | { 86 | $this->result[] = 'onShutdown'; 87 | } 88 | 89 | /** 90 | * @return array 91 | */ 92 | public function getResult() 93 | { 94 | return $this->result; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/EventProcessor/BatchEventProcessor/TestAsset/ExEventHandler.php: -------------------------------------------------------------------------------- 1 | latch->countDown(); 19 | throw new \Exception('Throws exception'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/EventProcessor/BatchEventProcessor/TestAsset/TestExceptionHandler.php: -------------------------------------------------------------------------------- 1 | result = new Threaded(); 22 | } 23 | 24 | /** 25 | * Strategy for handling uncaught exceptions when processing an event. 26 | * 27 | * If the strategy wishes to terminate further processing by the BatchEventProcessor 28 | * then it should throw a RuntimeException. 29 | * 30 | * @param Exception $ex the exception that propagated from the EventHandler. 31 | * @param int $sequence of the event which cause the exception. 32 | * @param object $event being processed when the exception occurred. This can be null. 33 | * @return void 34 | */ 35 | public function handleEventException(Exception $ex, $sequence, $event) 36 | { 37 | $this->result[] = __METHOD__ . get_class($ex) . '-' . $sequence . '-' . get_class($event); 38 | } 39 | 40 | /** 41 | * Callback to notify of an exception during LifecycleAware#onStart() 42 | * 43 | * @param Exception $ex throw during the starting process. 44 | * @return void 45 | */ 46 | public function handleOnStartException(Exception $ex) 47 | { 48 | $this->result[] = __METHOD__ . get_class($ex); 49 | } 50 | 51 | /** 52 | * Callback to notify of an exception during LifecycleAware#onShutdown() 53 | * 54 | * @param Exception $ex throw during the shutdown process. 55 | */ 56 | public function handleOnShutdownException(Exception $ex) 57 | { 58 | $this->result[] = __METHOD__ . get_class($ex); 59 | } 60 | 61 | /** 62 | * @return Threaded 63 | */ 64 | public function getResult() 65 | { 66 | return $this->result; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/EventPublisherTest.php: -------------------------------------------------------------------------------- 1 | eventFactory = new LongEventFactory; 31 | $this->ringBuffer = RingBuffer::createMultiProducer($this->eventFactory, self::RINGBUFFER_SIZE); 32 | } 33 | 34 | public function testShouldPublishEvent() 35 | { 36 | $eventProcessor = new NoOpEventProcessor($this->ringBuffer); 37 | $sequences = new SequenceList($eventProcessor->getSequence()); 38 | 39 | $this->ringBuffer->addGatingSequences($sequences); 40 | 41 | $this->ringBuffer->publishEvent($this); 42 | $this->ringBuffer->publishEvent($this); 43 | 44 | $this->assertEquals($this->ringBuffer->get(0)->get(), 29); 45 | $this->assertEquals($this->ringBuffer->get(1)->get(), 30); 46 | } 47 | 48 | public function testShouldTryPublishEvent() 49 | { 50 | $sequence = new Sequence(); 51 | $sequences = new SequenceList($sequence); 52 | $this->ringBuffer->addGatingSequences($sequences); 53 | 54 | for ($i = 0; $i < self::RINGBUFFER_SIZE; $i++) { 55 | $this->assertTrue($this->ringBuffer->tryPublishEvent($this)); 56 | } 57 | 58 | for ($i = 0; $i < self::RINGBUFFER_SIZE; $i++) { 59 | $this->assertEquals($this->ringBuffer->get($i)->get(), $i + 29); 60 | } 61 | 62 | $this->assertFalse($this->ringBuffer->tryPublishEvent($this)); 63 | } 64 | 65 | /** 66 | * Return the used event class name 67 | * 68 | * @return string 69 | */ 70 | public function getEventClass() 71 | { 72 | return __NAMESPACE__ . '\TestAsset\LongEvent'; 73 | } 74 | 75 | /** 76 | * Translate a data representation into fields set in given event 77 | * 78 | * @param object $event into which the data should be translated. 79 | * @param int $sequence that is assigned to event. 80 | * @param Threaded $args 81 | * @return void 82 | */ 83 | public function translateTo($event, $sequence, Threaded $args = null) 84 | { 85 | $event->set($sequence + 29); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/EventTranslatorTest.php: -------------------------------------------------------------------------------- 1 | newInstance(); 16 | 17 | $eventTranslator = new ExampleEventTranslator(self::TEST_VALUE); 18 | $eventTranslator->translateTo($event, 0); 19 | 20 | $this->assertEquals(self::TEST_VALUE, $event->getTestString()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/ExceptionHandler/FatalExceptionHandlerTest.php: -------------------------------------------------------------------------------- 1 | handleEventException($ex, 0, $event); 23 | } catch (RuntimeException $e) { 24 | $this->assertEquals($ex, $e->getPrevious()); 25 | } 26 | 27 | $res = file_get_contents(sys_get_temp_dir() . '/fatallog'); 28 | $this->assertEquals('ERR: Exception processing: 0 Test Event', $res); 29 | 30 | if (file_exists(sys_get_temp_dir() . '/fatallog')) { 31 | unlink(sys_get_temp_dir() . '/fatallog'); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/ExceptionHandler/IgnoreExceptionHandlerTest.php: -------------------------------------------------------------------------------- 1 | handleEventException($ex, 0, $event); 21 | 22 | $res = file_get_contents(sys_get_temp_dir() . '/ignorelog'); 23 | $this->assertEquals('INFO: Exception processing: 0 Test Event', $res); 24 | 25 | if (file_exists(sys_get_temp_dir() . '/ignorelog')) { 26 | unlink(sys_get_temp_dir() . '/ignorelog'); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/FixedSequenceGroupTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(34, $sequenceGroup->get()); 24 | $sequence1->set(35); 25 | $this->assertEquals(35, $sequenceGroup->get()); 26 | $sequence1->set(48); 27 | $this->assertEquals(47, $sequenceGroup->get()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/LifecycleAwareInterface/LifecylceAwareInterfaceTest.php: -------------------------------------------------------------------------------- 1 | newBarrier(); 22 | 23 | $handler = new LifecycleAwareEventHandler($startLatch, $shutdownLatch); 24 | 25 | $batchEventProcessor = new BatchEventProcessor( 26 | $eventFactory->getEventClass(), 27 | $ringBuffer, 28 | $sequenceBarrier, 29 | $handler 30 | ); 31 | 32 | $batchEventProcessor->start(); 33 | 34 | $startLatch->await(); 35 | $batchEventProcessor->halt(); 36 | 37 | $shutdownLatch->await(); 38 | 39 | $this->assertSame(1, $handler->startCounter); 40 | $this->assertSame(1, $handler->shutdownCounter); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/LifecycleAwareInterface/TestAsset/LifecycleAwareEventHandler.php: -------------------------------------------------------------------------------- 1 | startCounter = 0; 29 | $this->shutdownCounter = 0; 30 | 31 | $this->startLatch = $startLatch; 32 | $this->shutdownLatch = $shutdownLatch; 33 | } 34 | 35 | public function getEventClass() 36 | { 37 | return 'PhpDisruptorTest\TestAsset\StubEvent'; 38 | } 39 | 40 | public function onEvent($event, $sequence, $endOfBatch) 41 | { 42 | } 43 | 44 | public function onStart() 45 | { 46 | ++$this->startCounter; 47 | $this->startLatch->countDown(); 48 | } 49 | 50 | public function onShutdown() 51 | { 52 | ++$this->shutdownCounter; 53 | $this->shutdownLatch->countDown(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/MultiProducerSequencerTest.php: -------------------------------------------------------------------------------- 1 | publish(3); 15 | $publisher->publish(5); 16 | 17 | $this->assertFalse($publisher->isAvailable(0)); 18 | $this->assertFalse($publisher->isAvailable(1)); 19 | $this->assertFalse($publisher->isAvailable(2)); 20 | $this->assertTrue($publisher->isAvailable(3)); 21 | $this->assertFalse($publisher->isAvailable(4)); 22 | $this->assertTrue($publisher->isAvailable(5)); 23 | $this->assertFalse($publisher->isAvailable(6)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/SequenceBarrierTest.php: -------------------------------------------------------------------------------- 1 | eventFactory = new StubEventFactory(); 50 | $this->eventProcessor1 = new StubEventProcessor(); 51 | $this->eventProcessor2 = new StubEventProcessor(); 52 | $this->eventProcessor3 = new StubEventProcessor(); 53 | $this->ringBuffer = RingBuffer::createMultiProducer($this->eventFactory, 64); 54 | $eventProcessor = new NoOpEventProcessor($this->ringBuffer); 55 | $sequences = new SequenceList($eventProcessor->getSequence()); 56 | $this->ringBuffer->addGatingSequences($sequences); 57 | } 58 | 59 | public function testShouldWaitForWorkCompleteWhereCompleteWorkThresholdIsAhead() 60 | { 61 | $expectedNumberMessages = 10; 62 | $expectedWorkSequence = 9; 63 | $this->fillRingBuffer($expectedNumberMessages); 64 | 65 | $this->eventProcessor1->setSequence($expectedNumberMessages); 66 | $this->eventProcessor2->setSequence($expectedWorkSequence); 67 | $this->eventProcessor3->setSequence($expectedNumberMessages); 68 | 69 | $seqs = array( 70 | $this->eventProcessor1->getSequence(), 71 | $this->eventProcessor2->getSequence(), 72 | $this->eventProcessor3->getSequence() 73 | ); 74 | $sequences = new SequenceList($seqs); 75 | 76 | $sequenceBarrier = $this->ringBuffer->newBarrier($sequences); 77 | 78 | $completedWorkSequence = $sequenceBarrier->waitFor($expectedWorkSequence); 79 | $this->assertTrue($completedWorkSequence >= $expectedWorkSequence); 80 | } 81 | 82 | public function testShouldWaitForWorkCompleteWhereAllWorkersAreBlockedOnRingBuffer() 83 | { 84 | $expectedNumberMessages = 10; 85 | $this->fillRingBuffer($expectedNumberMessages); 86 | 87 | $workers = new EventProcessorList(); 88 | for ($i = 0; $i < 3; $i++) { 89 | $worker = new StubEventProcessor(); 90 | $worker->setSequence($expectedNumberMessages - 1); 91 | $workers->add($worker); 92 | } 93 | 94 | $sequenceBarrier = $this->ringBuffer->newBarrier(Util::getSequencesFor($workers)); 95 | $thread = new RingBufferThread($this->ringBuffer, $workers); 96 | $thread->start(); 97 | 98 | 99 | $expectedWorkSequence = $expectedNumberMessages; 100 | $completedWorkSequence = $sequenceBarrier->waitFor($expectedNumberMessages); 101 | $this->assertTrue($completedWorkSequence >= $expectedWorkSequence); 102 | 103 | $thread->join(); 104 | } 105 | 106 | public function testShouldInterruptDuringBusySpin() 107 | { 108 | $expectedNumberMessages = 10; 109 | $this->fillRingBuffer($expectedNumberMessages); 110 | 111 | $countDownLatch = new CountDownLatch(3); 112 | $sequence1 = new CountDownLatchSequence(8, $countDownLatch); 113 | $sequence2 = new CountDownLatchSequence(8, $countDownLatch); 114 | $sequence3 = new CountDownLatchSequence(8, $countDownLatch); 115 | 116 | $this->eventProcessor1->setSequenceObject($sequence1); 117 | $this->eventProcessor2->setSequenceObject($sequence2); 118 | $this->eventProcessor3->setSequenceObject($sequence3); 119 | 120 | $processors = new EventProcessorList(); 121 | $processors->add($this->eventProcessor1); 122 | $processors->add($this->eventProcessor2); 123 | $processors->add($this->eventProcessor3); 124 | 125 | $sequencesToTrack = Util::getSequencesFor($processors); 126 | $sequenceBarrier = $this->ringBuffer->newBarrier($sequencesToTrack); 127 | 128 | $alerted = new Threaded(); 129 | $alerted[0] = false; 130 | 131 | $thread = new SequenceBarrierThread($sequenceBarrier, $expectedNumberMessages, $alerted); 132 | $thread->start(); 133 | $countDownLatch->await(3000000); // 3sec 134 | $sequenceBarrier->alert(); 135 | $thread->join(); 136 | 137 | $this->assertTrue($alerted[0], 'Thread was not interrupted'); 138 | } 139 | 140 | public function testShouldWaitForWorkCompleteWhereCompleteWorkThresholdIsBehind() 141 | { 142 | $expectedNumberMessages = 10; 143 | $this->fillRingBuffer($expectedNumberMessages); 144 | 145 | $eventProcessors = new EventProcessorList(); 146 | for ($i = 0; $i < 3; $i++) { 147 | $eventProcessors[$i] = new StubEventProcessor(); 148 | $eventProcessors[$i]->setSequence($expectedNumberMessages - 2); 149 | } 150 | 151 | $sequenceBarrier = $this->ringBuffer->newBarrier(Util::getSequencesFor($eventProcessors)); 152 | 153 | $thread = new StubEventProcessorThread($eventProcessors); 154 | $thread->start(); 155 | $thread->join(); 156 | 157 | $expectedWorkSequence = $expectedNumberMessages - 1; 158 | $completedWorkSequence = $sequenceBarrier->waitFor($expectedWorkSequence); 159 | $this->assertTrue($completedWorkSequence >= $expectedWorkSequence); 160 | } 161 | 162 | public function testShouldSetAndClearAlertStatus() 163 | { 164 | $sequenceBarrier = $this->ringBuffer->newBarrier(); 165 | $this->assertFalse($sequenceBarrier->isAlerted()); 166 | 167 | $sequenceBarrier->alert(); 168 | $this->assertTrue($sequenceBarrier->isAlerted()); 169 | 170 | $sequenceBarrier->clearAlert(); 171 | $this->assertFalse($sequenceBarrier->isAlerted()); 172 | } 173 | 174 | protected function fillRingBuffer($expectedNumberMessages) 175 | { 176 | for ($i = 0; $i < $expectedNumberMessages; $i++) { 177 | $sequence = $this->ringBuffer->next(); 178 | $event = $this->ringBuffer->get($sequence); 179 | $event->setValue($i); 180 | $this->ringBuffer->publish($sequence); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/SequenceGroupTest.php: -------------------------------------------------------------------------------- 1 | get(); 18 | $this->assertEquals(PHP_INT_MAX, $result); 19 | } 20 | 21 | public function testShouldAddOneSequenceToGroup() 22 | { 23 | $sequence = new Sequence(7); 24 | $sequenceGroup = new SequenceGroup(); 25 | $sequenceGroup->add($sequence); 26 | 27 | $this->assertEquals($sequence->get(), $sequenceGroup->get()); 28 | } 29 | 30 | public function testShouldNotFailIfTryingToRemoveNotExistingSequence() 31 | { 32 | $sequenceGroup = new SequenceGroup(); 33 | $sequence1 = new Sequence(); 34 | $sequence2 = new Sequence(); 35 | $sequence3 = new Sequence(); 36 | $sequenceGroup->add($sequence1); 37 | $sequenceGroup->add($sequence2); 38 | $sequenceGroup->remove($sequence3); 39 | } 40 | 41 | public function testShouldReportTheMinimumSequenceForGroupOfTwo() 42 | { 43 | $sequenceThree = new Sequence(3); 44 | $sequenceSeven = new Sequence(7); 45 | $sequenceGroup = new SequenceGroup(); 46 | 47 | $sequenceGroup->add($sequenceSeven); 48 | $sequenceGroup->add($sequenceThree); 49 | 50 | $this->assertEquals($sequenceThree->get(), $sequenceGroup->get()); 51 | } 52 | 53 | public function testShouldReportSizeOfGroup() 54 | { 55 | $sequenceGroup = new SequenceGroup(); 56 | $sequence1 = new Sequence(); 57 | $sequence2 = new Sequence(); 58 | $sequence3 = new Sequence(); 59 | $sequenceGroup->add($sequence1); 60 | $sequenceGroup->add($sequence2); 61 | $sequenceGroup->add($sequence3); 62 | $this->assertEquals(3, $sequenceGroup->count()); 63 | } 64 | 65 | public function testShouldRemoveSequenceFromGroup() 66 | { 67 | $sequenceThree = new Sequence(3); 68 | $sequenceSeven = new Sequence(7); 69 | $sequenceGroup = new SequenceGroup(); 70 | 71 | $sequenceGroup->add($sequenceThree); 72 | $sequenceGroup->add($sequenceSeven); 73 | $this->assertEquals($sequenceThree->get(), $sequenceGroup->get()); 74 | $this->assertTrue($sequenceGroup->remove($sequenceThree)); 75 | $this->assertEquals($sequenceSeven->get(), $sequenceGroup->get()); 76 | $this->assertEquals(1, $sequenceGroup->count()); 77 | } 78 | 79 | public function testShouldDoNothingWhenRemovingNotContainingSequence() 80 | { 81 | $sequenceThree = new Sequence(3); 82 | $sequenceGroup = new SequenceGroup(); 83 | 84 | $this->assertFalse($sequenceGroup->remove($sequenceThree)); 85 | } 86 | 87 | public function testShouldRemoveSequenceFromGroupWhereItBeenAddedMultipleTimes() 88 | { 89 | $sequenceThree = new Sequence(3); 90 | $sequenceSeven = new Sequence(7); 91 | $sequenceGroup = new SequenceGroup(); 92 | 93 | $sequenceGroup->add($sequenceThree); 94 | $sequenceGroup->add($sequenceSeven); 95 | $sequenceGroup->add($sequenceThree); 96 | 97 | $this->assertEquals($sequenceThree->get(), $sequenceGroup->get()); 98 | 99 | $this->assertTrue($sequenceGroup->remove($sequenceThree)); 100 | $this->assertEquals($sequenceSeven->get(), $sequenceGroup->get()); 101 | $this->assertEquals(1, $sequenceGroup->count()); 102 | } 103 | 104 | public function testShouldSetGroupSequenceToSameValue() 105 | { 106 | $sequenceThree = new Sequence(3); 107 | $sequenceSeven = new Sequence(7); 108 | $sequenceGroup = new SequenceGroup(); 109 | 110 | $sequenceGroup->add($sequenceSeven); 111 | $sequenceGroup->add($sequenceThree); 112 | 113 | $expectedSequence = 11; 114 | $sequenceGroup->set($expectedSequence); 115 | 116 | $this->assertEquals($expectedSequence, $sequenceThree->get()); 117 | $this->assertEquals($expectedSequence, $sequenceSeven->get()); 118 | } 119 | 120 | public function testObjectEqualityThroughThreads() 121 | { 122 | $sequence = new Sequence(); 123 | $sequenceGroup = new SequenceGroup(); 124 | $sequenceGroup->add($sequence); 125 | $sequences = $sequenceGroup->getSequences(); 126 | $groupSequence = $sequences[0]; 127 | 128 | $this->assertSame($sequence, $groupSequence); 129 | $this->assertNotSame($sequence, $sequenceGroup); 130 | $this->assertSame($sequence, $groupSequence); 131 | } 132 | 133 | 134 | public function testShouldAddWhileRunning() 135 | { 136 | $eventFactory = new TestEventFactory(); 137 | $ringBuffer = RingBuffer::createSingleProducer($eventFactory, 32); 138 | $sequenceThree = new Sequence(3); 139 | $sequenceSeven = new Sequence(7); 140 | $sequenceGroup = new SequenceGroup(); 141 | $sequenceGroup->add($sequenceSeven); 142 | 143 | for ($i = 0; $i < 11; $i++) 144 | { 145 | $ringBuffer->publish($ringBuffer->next()); 146 | } 147 | 148 | $sequenceGroup->addWhileRunning($ringBuffer, $sequenceThree); 149 | $this->assertEquals(10, $sequenceThree->get()); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/SequenceReportingCallbackTest.php: -------------------------------------------------------------------------------- 1 | newBarrier(); 22 | $handler = new TestSequenceReportingEventHandler($callbackLatch); 23 | $batchEventProcessor = new BatchEventProcessor($eventFactory->getEventClass(), $ringBuffer, $sequenceBarrier, $handler); 24 | 25 | $sequenceList = new SequenceList($batchEventProcessor->getSequence()); 26 | $ringBuffer->addGatingSequences($sequenceList); 27 | 28 | $batchEventProcessor->start(); 29 | 30 | $this->assertSame(-1, $batchEventProcessor->getSequence()->get()); 31 | $ringBuffer->publish($ringBuffer->next()); 32 | 33 | $callbackLatch->await(); 34 | $this->assertSame(0, $batchEventProcessor->getSequence()->get()); 35 | 36 | $onEndOfBatchLatch->countDown(); 37 | $this->assertSame(0, $batchEventProcessor->getSequence()->get()); 38 | 39 | $batchEventProcessor->halt(); 40 | $batchEventProcessor->join(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/ArrayEventTranslator.php: -------------------------------------------------------------------------------- 1 | size = $size; 15 | } 16 | 17 | /** 18 | * Return the used event class name 19 | * 20 | * @return string 21 | */ 22 | public function getEventClass() 23 | { 24 | return 'Threaded'; 25 | } 26 | 27 | public function newInstance() 28 | { 29 | $array = new Threaded(); 30 | for ($i = 0; $i < $this->size; $i++) { 31 | $array[] = null; 32 | } 33 | return $array; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/CountDownLatchSequence.php: -------------------------------------------------------------------------------- 1 | latch = $latch; 23 | } 24 | 25 | /** 26 | * @return int 27 | */ 28 | public function get() 29 | { 30 | $this->latch->countDown(); 31 | return parent::get(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/EventTranslator.php: -------------------------------------------------------------------------------- 1 | value = $value; 15 | } 16 | 17 | /** 18 | * Return the used event class name 19 | * 20 | * @return string 21 | */ 22 | public function getEventClass() 23 | { 24 | return 'PhpDisruptorTest\TestAsset\StubEvent'; 25 | } 26 | 27 | /** 28 | * Translate a data representation into fields set in given event 29 | * 30 | * @param object $event into which the data should be translated. 31 | * @param int $sequence that is assigned to event. 32 | * @param Threaded|null $args 33 | * @return void 34 | */ 35 | public function translateTo($event, $sequence, Threaded $args = null) 36 | { 37 | $event->setTestString($this->value); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/LongEvent.php: -------------------------------------------------------------------------------- 1 | long = 0; 14 | } 15 | 16 | public function set($long) 17 | { 18 | $this->long = $long; 19 | } 20 | 21 | public function get() 22 | { 23 | return $this->long; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/LongEventFactory.php: -------------------------------------------------------------------------------- 1 | ringBuffer = $ringBuffer; 17 | $this->workers = $workers; 18 | } 19 | 20 | public function run() 21 | { 22 | $sequence = $this->ringBuffer->next(); 23 | $event = $this->ringBuffer->get($sequence); 24 | $event->setValue($sequence); 25 | $this->ringBuffer->publish($sequence); 26 | foreach ($this->workers as $worker) { 27 | $worker->setSequence($sequence); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/RingBufferThread2.php: -------------------------------------------------------------------------------- 1 | ringBuffer = $ringBuffer; 21 | $this->latch = $latch; 22 | $this->publisherComplete = $publisherComplete; 23 | } 24 | 25 | public function run() 26 | { 27 | $ringBuffer = $this->ringBuffer; 28 | for ($i = 0; $i <= $ringBuffer->getBufferSize(); $i++) { 29 | $sequence = $ringBuffer->next(); 30 | $event = $ringBuffer->get($sequence); 31 | /* @var $event StubEvent */ 32 | $event->setValue($i); 33 | $ringBuffer->publish($sequence); 34 | $this->latch->countDown(); 35 | } 36 | $this->publisherComplete[0] = true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/SequenceBarrierThread.php: -------------------------------------------------------------------------------- 1 | barrier = $barrier; 20 | $this->expectedNumberOfMessages = $expectedNumberOfMessages; 21 | $this->alerted = $alerted; 22 | } 23 | 24 | public function run() 25 | { 26 | try { 27 | $this->barrier->waitFor($this->expectedNumberOfMessages - 1); 28 | } catch (AlertException $e) { 29 | $this->alerted[0] = true; 30 | } catch (\Exception $e) { 31 | // ignore 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/SleepingEventHandler.php: -------------------------------------------------------------------------------- 1 | eventClass = $eventClass; 24 | } 25 | 26 | /** 27 | * Return the used event class name 28 | * 29 | * @return string 30 | */ 31 | public function getEventClass() 32 | { 33 | return $this->eventClass; 34 | } 35 | 36 | /** 37 | * Called when a publisher has published an event to the RingBuffer 38 | * 39 | * @param object $event published to the RingBuffer 40 | * @param int $sequence of the event being processed 41 | * @param bool $endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer 42 | * @return void 43 | * @throws Exception\ExceptionInterface if the EventHandler would like the exception handled further up the chain. 44 | */ 45 | public function onEvent($event, $sequence, $endOfBatch) 46 | { 47 | $this->wait(1); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/StubEvent.php: -------------------------------------------------------------------------------- 1 | value = $value; 27 | } 28 | 29 | public function copy(self $event) 30 | { 31 | $this->value = $event->value; 32 | } 33 | 34 | public function getValue() 35 | { 36 | return $this->value; 37 | } 38 | 39 | public function setValue($value) 40 | { 41 | $this->value = $value; 42 | } 43 | 44 | public function getTestString() 45 | { 46 | return $this->testString; 47 | } 48 | 49 | public function setTestString($testString) 50 | { 51 | $this->testString = $testString; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/StubEventFactory.php: -------------------------------------------------------------------------------- 1 | sequence = new Sequence(); 18 | } 19 | 20 | public function setSequence($sequence) 21 | { 22 | $this->sequence->set($sequence); 23 | } 24 | 25 | public function setSequenceObject(Sequence $se) 26 | { 27 | $this->sequence = $se; 28 | } 29 | 30 | /** 31 | * Get a reference to the Sequence being used by this EventProcessor. 32 | * 33 | * @return Sequence reference to the Sequence for this EventProcessor 34 | */ 35 | public function getSequence() 36 | { 37 | return $this->sequence; 38 | } 39 | 40 | /** 41 | * @return void 42 | */ 43 | public function halt() 44 | { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/StubEventProcessorThread.php: -------------------------------------------------------------------------------- 1 | eventProcessors = $eventProcessors; 14 | } 15 | 16 | public function run() 17 | { 18 | foreach ($this->eventProcessors as $stubWorker) { 19 | $stubWorker->setSequence($stubWorker->getSequence()->get() + 1); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/StubEventTranslator.php: -------------------------------------------------------------------------------- 1 | setValue($args[0]); 36 | $event->setTestString($args[1]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/TestEvent.php: -------------------------------------------------------------------------------- 1 | sequence = $sequence; 15 | } 16 | 17 | /** 18 | * Get a reference to the Sequence being used by this EventProcessor. 19 | * 20 | * @return Sequence reference to the Sequence for this EventProcessor 21 | */ 22 | public function getSequence() 23 | { 24 | return $this->sequence; 25 | } 26 | 27 | public function halt() 28 | { 29 | } 30 | } -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/TestEventProcessor2.php: -------------------------------------------------------------------------------- 1 | sequenceBarrier = $sequenceBarrier; 18 | $this->sequence = new Sequence(); 19 | } 20 | 21 | /** 22 | * Get a reference to the Sequence being used by this EventProcessor. 23 | * 24 | * @return Sequence reference to the Sequence for this EventProcessor 25 | */ 26 | public function getSequence() 27 | { 28 | return $this->sequence; 29 | } 30 | 31 | /** 32 | * @return void 33 | */ 34 | public function halt() 35 | { 36 | } 37 | 38 | public function run() 39 | { 40 | try { 41 | $this->sequenceBarrier->waitFor(0); 42 | } catch (\Exception $e) { 43 | throw new \RuntimeException('', 0, $e); 44 | } 45 | 46 | $newSequence = $this->sequence->get() + 1; 47 | $this->sequence->set($newSequence); 48 | } 49 | } -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/TestAsset/TestSequenceReportingEventHandler.php: -------------------------------------------------------------------------------- 1 | latch = $latch; 24 | } 25 | 26 | public function getEventClass() 27 | { 28 | return __NAMESPACE__ . '\StubEvent'; 29 | } 30 | 31 | public function setSequenceCallback(Sequence $sequenceTrackerCallback) 32 | { 33 | $this->sequenceCallback = $sequenceTrackerCallback; 34 | } 35 | 36 | public function onEvent($event, $sequence, $endOfBatch) 37 | { 38 | $this->sequenceCallback->set($sequence); 39 | $this->latch->countDown(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/Util/UtilTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(1024, $powerOfTwo); 17 | } 18 | 19 | public function testShouldReturnExactPowerOfTwo() 20 | { 21 | $powerOfTwo = Util::ceilingNextPowerOfTwo(1024); 22 | $this->assertEquals(1024, $powerOfTwo); 23 | } 24 | 25 | public function testLog2Of23() 26 | { 27 | $log2 = Util::log2(23); 28 | $this->assertEquals(4, $log2); 29 | } 30 | 31 | public function testLog2Of1000() 32 | { 33 | $log2 = Util::log2(1000); 34 | $this->assertEquals(9, $log2); 35 | } 36 | 37 | public function dataProvider() 38 | { 39 | $sequences1 = array( 40 | new Sequence(3), 41 | new Sequence(5), 42 | new Sequence(7) 43 | ); 44 | $one = new SequenceList($sequences1); 45 | 46 | $sequences2 = array( 47 | new Sequence(7), 48 | new Sequence(5), 49 | new Sequence(3) 50 | ); 51 | $two = new SequenceList($sequences2); 52 | 53 | $sequences3 = array( 54 | new Sequence(5), 55 | new Sequence(7), 56 | new Sequence(3) 57 | ); 58 | $three = new SequenceList($sequences3); 59 | 60 | return array( 61 | array( 62 | $one 63 | ), 64 | array( 65 | $two 66 | ), 67 | array( 68 | $three 69 | ) 70 | ); 71 | } 72 | 73 | /** 74 | * @dataProvider dataProvider 75 | */ 76 | public function testShouldReturnMinimumSequence($data) 77 | { 78 | $this->assertEquals(3, Util::getMinimumSequence($data)); 79 | } 80 | 81 | public function testShouldReturnPhpIntMaxWhenNoEventProcessors() 82 | { 83 | $sequences = new SequenceList(); 84 | $this->assertEquals(PHP_INT_MAX, Util::getMinimumSequence($sequences)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/WaitStrategy/AbstractWaitStrategyTestCase.php: -------------------------------------------------------------------------------- 1 | start(); 16 | $sequenceUpdater->waitForStartup(); 17 | $cursor = new Sequence(0); 18 | $s = $sequenceUpdater->getSequence(); 19 | $barrier = new DummySequenceBarrier(); 20 | $sequence = $waitStrategy->waitFor(0, $cursor, $s, $barrier); 21 | 22 | $this->assertSame(0, $sequence); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/WaitStrategy/BusySpinWaitStrategyTest.php: -------------------------------------------------------------------------------- 1 | assertWaitForWithDelayOf(50000, $strategy); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/WaitStrategy/SleepingWaitStrategyTest.php: -------------------------------------------------------------------------------- 1 | assertWaitForWithDelayOf(50000, $strategy); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/WaitStrategy/TestAsset/DummySequenceBarrier.php: -------------------------------------------------------------------------------- 1 | sleepTimeMicros = $sleepTimeMicros; 22 | $this->waitStrategy = $waitStrategy; 23 | $this->barrier = new CyclicBarrier(2); 24 | $this->sequence = new Sequence(); 25 | } 26 | 27 | public function run() 28 | { 29 | $this->barrier->await(); 30 | if (0 != $this->sleepTimeMicros) { 31 | $this->wait($this->sleepTimeMicros); 32 | } 33 | $this->sequence->incrementAndGet(); 34 | $this->waitStrategy->signalAllWhenBlocking(); 35 | } 36 | 37 | public function waitForStartup() 38 | { 39 | $this->barrier->await(); 40 | } 41 | 42 | public function getSequence() 43 | { 44 | return $this->sequence; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/PhpDisruptorTest/WaitStrategy/YieldingWaitStrategyTest.php: -------------------------------------------------------------------------------- 1 | assertWaitForWithDelayOf(50000, $strategy); 13 | } 14 | } 15 | --------------------------------------------------------------------------------