Repository: LMAX-Exchange/disruptor Branch: master Commit: c871ca49826a Files: 283 Total size: 1.1 MB Directory structure: gitextract_mzd229g4/ ├── .editorconfig ├── .githooks/ │ └── pre-commit ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ ├── asciidoc-build-only.yml │ ├── asciidoc.yml │ ├── codeql-analysis.yml │ ├── gradle-build.yml │ ├── gradle-wrapper-validation.yml │ ├── jcstress-manual.yml │ └── jcstress-quick.yml ├── .gitignore ├── .lgtm.yml ├── CHANGELOG.adoc ├── LICENCE.txt ├── README.adoc ├── build.gradle ├── config/ │ └── checkstyle/ │ ├── checkstyle.xml │ └── suppress.xml ├── gradle/ │ ├── asciidoc.gradle │ ├── jcstress.gradle │ ├── jmh.gradle │ ├── maven.gradle │ ├── perf.gradle │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src/ ├── docs/ │ └── asciidoc/ │ └── en/ │ ├── changelog.adoc │ ├── developer-guide/ │ │ ├── 10_getting_and_building.adoc │ │ ├── 20_performance_tests.adoc │ │ ├── 25_jsctress_tests.adoc │ │ ├── 30_publishing_release.adoc │ │ ├── 90_tips.adoc │ │ └── index.adoc │ ├── disruptor.adoc │ ├── index.adoc │ └── user-guide/ │ ├── 10_using_the_disruptor.adoc │ ├── 20_design_and_implementation.adoc │ ├── 30_known_issues.adoc │ ├── 40_batch_rewind_use_case.adoc │ └── index.adoc ├── examples/ │ └── java/ │ └── com/ │ └── lmax/ │ └── disruptor/ │ └── examples/ │ ├── DynamicallyAddHandler.java │ ├── EarlyReleaseHandler.java │ ├── HandleExceptionOnTranslate.java │ ├── KeyedBatching.java │ ├── MultiProducerWithTranslator.java │ ├── NamedEventHandler.java │ ├── Pipeliner.java │ ├── PullWithBatchedPoller.java │ ├── PullWithPoller.java │ ├── SequentialThreeConsumers.java │ ├── ShutdownOnError.java │ ├── ThreeToOneDisruptor.java │ ├── WaitForProcessing.java │ ├── WaitForShutdown.java │ ├── longevent/ │ │ ├── LongEvent.java │ │ ├── LongEventFactory.java │ │ ├── LongEventHandler.java │ │ ├── LongEventProducer.java │ │ ├── LongEventProducerWithTranslator.java │ │ ├── lambdas/ │ │ │ └── LongEventMain.java │ │ ├── legacy/ │ │ │ ├── LongEventMain.java │ │ │ └── LongEventProducer.java │ │ └── methodrefs/ │ │ └── LongEventMain.java │ ├── objectevent/ │ │ ├── ClearingEventHandler.java │ │ ├── Main.java │ │ ├── ObjectEvent.java │ │ └── ProcessingEventHandler.java │ └── support/ │ ├── LongEvent.java │ └── StubEvent.java ├── jcstress/ │ └── java/ │ └── com/ │ └── lmax/ │ └── disruptor/ │ ├── LoggerInitializationStress.java │ ├── MultiProducerSequencerUnsafeStress.java │ ├── MultiProducerSequencerVarHandleStress.java │ ├── SequenceStressUnsafe.java │ ├── SequenceStressVarHandle.java │ └── SequenceStressVarHandleBarrier.java ├── jmh/ │ └── java/ │ └── com/ │ └── lmax/ │ └── disruptor/ │ ├── ArrayAccessBenchmark.java │ ├── BlockingQueueBenchmark.java │ ├── MultiProducerSequencerBenchmark.java │ ├── MultiProducerSingleConsumer.java │ ├── RingBufferBenchmark.java │ ├── RingBufferFalseSharingBenchmark.java │ ├── SequenceBenchmark.java │ ├── SingleProducerSingleConsumer.java │ └── util/ │ ├── Constants.java │ ├── SimpleEvent.java │ └── SimpleEventHandler.java ├── main/ │ └── java/ │ ├── com/ │ │ └── lmax/ │ │ └── disruptor/ │ │ ├── AbstractSequencer.java │ │ ├── AggregateEventHandler.java │ │ ├── AlertException.java │ │ ├── BatchEventProcessor.java │ │ ├── BatchEventProcessorBuilder.java │ │ ├── BatchRewindStrategy.java │ │ ├── BlockingWaitStrategy.java │ │ ├── BusySpinWaitStrategy.java │ │ ├── Cursored.java │ │ ├── DataProvider.java │ │ ├── EventFactory.java │ │ ├── EventHandler.java │ │ ├── EventHandlerBase.java │ │ ├── EventHandlerIdentity.java │ │ ├── EventPoller.java │ │ ├── EventProcessor.java │ │ ├── EventSequencer.java │ │ ├── EventSink.java │ │ ├── EventTranslator.java │ │ ├── EventTranslatorOneArg.java │ │ ├── EventTranslatorThreeArg.java │ │ ├── EventTranslatorTwoArg.java │ │ ├── EventTranslatorVararg.java │ │ ├── EventuallyGiveUpBatchRewindStrategy.java │ │ ├── ExceptionHandler.java │ │ ├── ExceptionHandlers.java │ │ ├── FatalExceptionHandler.java │ │ ├── FixedSequenceGroup.java │ │ ├── IgnoreExceptionHandler.java │ │ ├── InsufficientCapacityException.java │ │ ├── LiteBlockingWaitStrategy.java │ │ ├── LiteTimeoutBlockingWaitStrategy.java │ │ ├── MultiProducerSequencer.java │ │ ├── NanosecondPauseBatchRewindStrategy.java │ │ ├── NoOpEventProcessor.java │ │ ├── PhasedBackoffWaitStrategy.java │ │ ├── ProcessingSequenceBarrier.java │ │ ├── RewindAction.java │ │ ├── RewindHandler.java │ │ ├── RewindableEventHandler.java │ │ ├── RewindableException.java │ │ ├── RingBuffer.java │ │ ├── Sequence.java │ │ ├── SequenceBarrier.java │ │ ├── SequenceGroup.java │ │ ├── SequenceGroups.java │ │ ├── Sequenced.java │ │ ├── Sequencer.java │ │ ├── SimpleBatchRewindStrategy.java │ │ ├── SingleProducerSequencer.java │ │ ├── SleepingWaitStrategy.java │ │ ├── TimeoutBlockingWaitStrategy.java │ │ ├── TimeoutException.java │ │ ├── WaitStrategy.java │ │ ├── YieldingWaitStrategy.java │ │ ├── dsl/ │ │ │ ├── ConsumerInfo.java │ │ │ ├── ConsumerRepository.java │ │ │ ├── Disruptor.java │ │ │ ├── EventHandlerGroup.java │ │ │ ├── EventProcessorFactory.java │ │ │ ├── EventProcessorInfo.java │ │ │ ├── ExceptionHandlerSetting.java │ │ │ ├── ExceptionHandlerWrapper.java │ │ │ ├── ProducerType.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── util/ │ │ ├── DaemonThreadFactory.java │ │ ├── ThreadHints.java │ │ ├── Util.java │ │ └── package-info.java │ └── module-info.java ├── perftest/ │ └── java/ │ └── com/ │ └── lmax/ │ └── disruptor/ │ ├── AbstractPerfTestDisruptor.java │ ├── AbstractPerfTestQueue.java │ ├── PerfTestContext.java │ ├── immutable/ │ │ ├── Constants.java │ │ ├── CustomPerformanceTest.java │ │ ├── CustomRingBuffer.java │ │ ├── EventAccessor.java │ │ ├── EventHolder.java │ │ ├── EventHolderHandler.java │ │ ├── SimpleEvent.java │ │ ├── SimpleEventHandler.java │ │ └── SimplePerformanceTest.java │ ├── offheap/ │ │ ├── OneToOneOffHeapThroughputTest.java │ │ └── OneToOneOnHeapThroughputTest.java │ ├── queue/ │ │ ├── OneToOneQueueBatchedThroughputTest.java │ │ ├── OneToOneQueueThroughputTest.java │ │ ├── OneToThreeDiamondQueueThroughputTest.java │ │ ├── OneToThreePipelineQueueThroughputTest.java │ │ ├── OneToThreeQueueThroughputTest.java │ │ ├── PingPongQueueLatencyTest.java │ │ ├── ThreeToOneQueueBatchThroughputTest.java │ │ └── ThreeToOneQueueThroughputTest.java │ ├── raw/ │ │ ├── OneToOneRawBatchThroughputTest.java │ │ └── OneToOneRawThroughputTest.java │ ├── sequenced/ │ │ ├── OneToOneSequencedBatchThroughputTest.java │ │ ├── OneToOneSequencedLongArrayThroughputTest.java │ │ ├── OneToOneSequencedPollerThroughputTest.java │ │ ├── OneToOneSequencedThroughputTest.java │ │ ├── OneToThreeDiamondSequencedThroughputTest.java │ │ ├── OneToThreePipelineSequencedThroughputTest.java │ │ ├── OneToThreeSequencedThroughputTest.java │ │ ├── PingPongSequencedLatencyTest.java │ │ ├── ThreeToOneSequencedBatchThroughputTest.java │ │ ├── ThreeToOneSequencedThroughputTest.java │ │ └── ThreeToThreeSequencedThroughputTest.java │ ├── support/ │ │ ├── EventCountingQueueProcessor.java │ │ ├── FizzBuzzEvent.java │ │ ├── FizzBuzzEventHandler.java │ │ ├── FizzBuzzQueueProcessor.java │ │ ├── FizzBuzzStep.java │ │ ├── FunctionEvent.java │ │ ├── FunctionEventHandler.java │ │ ├── FunctionQueueProcessor.java │ │ ├── FunctionStep.java │ │ ├── LongArrayEventHandler.java │ │ ├── LongArrayPublisher.java │ │ ├── MultiBufferBatchEventProcessor.java │ │ ├── Operation.java │ │ ├── PerfTestUtil.java │ │ ├── ValueAdditionBatchQueueProcessor.java │ │ ├── ValueAdditionEventHandler.java │ │ ├── ValueAdditionQueueBatchProcessor.java │ │ ├── ValueAdditionQueueProcessor.java │ │ ├── ValueBatchPublisher.java │ │ ├── ValueEvent.java │ │ ├── ValueMutationEventHandler.java │ │ ├── ValueMutationQueueProcessor.java │ │ ├── ValuePublisher.java │ │ └── ValueQueuePublisher.java │ └── translator/ │ └── OneToOneTranslatorThroughputTest.java └── test/ └── java/ └── com/ └── lmax/ └── disruptor/ ├── AggregateEventHandlerTest.java ├── BatchEventProcessorTest.java ├── BatchingTest.java ├── BusySpinWaitStrategyTest.java ├── DisruptorStressTest.java ├── EventPollerTest.java ├── EventPublisherTest.java ├── EventTranslatorTest.java ├── FatalExceptionHandlerTest.java ├── FixedSequenceGroupTest.java ├── IgnoreExceptionHandlerTest.java ├── LifecycleAwareTest.java ├── LiteTimeoutBlockingWaitStrategyTest.java ├── MaxBatchSizeEventProcessorTest.java ├── MultiProducerSequencerTest.java ├── PhasedBackoffWaitStrategyTest.java ├── RewindBatchEventProcessorTest.java ├── RingBufferEventMatcher.java ├── RingBufferTest.java ├── RingBufferWithAssertingStubTest.java ├── SequenceBarrierTest.java ├── SequenceGroupTest.java ├── SequenceReportingCallbackTest.java ├── SequenceTest.java ├── SequencerTest.java ├── ShutdownOnFatalExceptionTest.java ├── SingleProducerSequencerTest.java ├── SleepingWaitStrategyTest.java ├── TimeoutBlockingWaitStrategyTest.java ├── YieldingWaitStrategyTest.java ├── alternatives/ │ ├── MultiProducerSequencerUnsafe.java │ ├── MultiProducerSequencerVarHandle.java │ ├── RingBufferArray.java │ ├── RingBufferUnsafe.java │ ├── SequenceDoublePadded.java │ ├── SequenceUnsafe.java │ ├── SequenceVarHandle.java │ ├── SequenceVarHandleArray.java │ └── SequenceVarHandleBarrier.java ├── dsl/ │ ├── ConsumerRepositoryTest.java │ ├── DisruptorTest.java │ └── stubs/ │ ├── DelayedEventHandler.java │ ├── EventHandlerStub.java │ ├── EvilEqualsEventHandler.java │ ├── ExceptionThrowingEventHandler.java │ ├── SleepingEventHandler.java │ ├── StubExceptionHandler.java │ ├── StubPublisher.java │ └── StubThreadFactory.java ├── support/ │ ├── DummyEventHandler.java │ ├── DummyEventProcessor.java │ ├── DummySequenceBarrier.java │ ├── DummyWaitStrategy.java │ ├── LongEvent.java │ ├── SequenceUpdater.java │ ├── StubEvent.java │ ├── TestEvent.java │ ├── TestWaiter.java │ └── WaitStrategyTestUtil.java └── util/ ├── MutableLong.java ├── PaddedLong.java ├── UnsafeAccess.java └── UtilTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # noinspection EditorConfigKeyCorrectness [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = false max_line_length = 120 tab_width = 4 trim_trailing_whitespace = true curly_bracket_next_line=true spaces_around_operators=true indent_brace_style=Allman ij_continuation_indent_size = 8 ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on ij_formatter_tags_enabled = false ij_smart_tabs = false ij_wrap_on_typing = false [*.css] ij_css_align_closing_brace_with_properties = false ij_css_blank_lines_around_nested_selector = 1 ij_css_blank_lines_between_blocks = 1 ij_css_brace_placement = end_of_line ij_css_enforce_quotes_on_format = false ij_css_hex_color_long_format = false ij_css_hex_color_lower_case = false ij_css_hex_color_short_format = false ij_css_hex_color_upper_case = false ij_css_keep_blank_lines_in_code = 2 ij_css_keep_indents_on_empty_lines = false ij_css_keep_single_line_blocks = false ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_css_space_after_colon = true ij_css_space_before_opening_brace = true ij_css_use_double_quotes = true ij_css_value_alignment = do_not_align [*.java] ij_java_align_consecutive_assignments = false ij_java_align_consecutive_variable_declarations = false ij_java_align_group_field_declarations = false ij_java_align_multiline_annotation_parameters = false ij_java_align_multiline_array_initializer_expression = false ij_java_align_multiline_assignment = false ij_java_align_multiline_binary_operation = false ij_java_align_multiline_chained_methods = false ij_java_align_multiline_extends_list = false ij_java_align_multiline_for = true ij_java_align_multiline_method_parentheses = false ij_java_align_multiline_parameters = true ij_java_align_multiline_parameters_in_calls = false ij_java_align_multiline_parenthesized_expression = false ij_java_align_multiline_records = true ij_java_align_multiline_resources = true ij_java_align_multiline_ternary_operation = false ij_java_align_multiline_text_blocks = false ij_java_align_multiline_throws_list = false ij_java_align_subsequent_simple_methods = false ij_java_align_throws_keyword = false ij_java_annotation_parameter_wrap = off ij_java_array_initializer_new_line_after_left_brace = false ij_java_array_initializer_right_brace_on_new_line = false ij_java_array_initializer_wrap = off ij_java_assert_statement_colon_on_next_line = false ij_java_assert_statement_wrap = off ij_java_assignment_wrap = off ij_java_binary_operation_sign_on_next_line = false ij_java_binary_operation_wrap = off ij_java_blank_lines_after_anonymous_class_header = 0 ij_java_blank_lines_after_class_header = 0 ij_java_blank_lines_after_imports = 1 ij_java_blank_lines_after_package = 1 ij_java_blank_lines_around_class = 1 ij_java_blank_lines_around_field = 0 ij_java_blank_lines_around_field_in_interface = 0 ij_java_blank_lines_around_initializer = 1 ij_java_blank_lines_around_method = 1 ij_java_blank_lines_around_method_in_interface = 1 ij_java_blank_lines_before_class_end = 0 ij_java_blank_lines_before_imports = 1 ij_java_blank_lines_before_method_body = 0 ij_java_blank_lines_before_package = 0 ij_java_block_brace_style = next_line ij_java_block_comment_at_first_column = true ij_java_call_parameters_new_line_after_left_paren = false ij_java_call_parameters_right_paren_on_new_line = false ij_java_call_parameters_wrap = off ij_java_case_statement_on_separate_line = true ij_java_catch_on_new_line = true ij_java_class_annotation_wrap = split_into_lines ij_java_class_brace_style = next_line ij_java_class_count_to_use_import_on_demand = 99 ij_java_class_names_in_javadoc = 1 ij_java_do_not_indent_top_level_class_members = false ij_java_do_not_wrap_after_single_annotation = false ij_java_do_while_brace_force = always ij_java_doc_add_blank_line_after_description = true ij_java_doc_add_blank_line_after_param_comments = false ij_java_doc_add_blank_line_after_return = false ij_java_doc_add_p_tag_on_empty_lines = true ij_java_doc_align_exception_comments = true ij_java_doc_align_param_comments = true ij_java_doc_do_not_wrap_if_one_line = false ij_java_doc_enable_formatting = true ij_java_doc_enable_leading_asterisks = true ij_java_doc_indent_on_continuation = false ij_java_doc_keep_empty_lines = true ij_java_doc_keep_empty_parameter_tag = true ij_java_doc_keep_empty_return_tag = true ij_java_doc_keep_empty_throws_tag = true ij_java_doc_keep_invalid_tags = true ij_java_doc_param_description_on_new_line = false ij_java_doc_preserve_line_breaks = false ij_java_doc_use_throws_not_exception_tag = true ij_java_else_on_new_line = true ij_java_entity_dd_suffix = EJB ij_java_entity_eb_suffix = Bean ij_java_entity_hi_suffix = Home ij_java_entity_lhi_prefix = Local ij_java_entity_lhi_suffix = Home ij_java_entity_li_prefix = Local ij_java_entity_pk_class = java.lang.String ij_java_entity_vo_suffix = VO ij_java_enum_constants_wrap = off ij_java_extends_keyword_wrap = off ij_java_extends_list_wrap = off ij_java_field_annotation_wrap = split_into_lines ij_java_finally_on_new_line = true ij_java_for_brace_force = always ij_java_for_statement_new_line_after_left_paren = false ij_java_for_statement_right_paren_on_new_line = false ij_java_for_statement_wrap = off ij_java_generate_final_locals = true ij_java_generate_final_parameters = true ij_java_if_brace_force = always ij_java_imports_layout = *,|,javax.**,java.**,|,$* ij_java_indent_case_from_switch = true ij_java_insert_inner_class_imports = false ij_java_insert_override_annotation = true ij_java_keep_blank_lines_before_right_brace = 2 ij_java_keep_blank_lines_between_package_declaration_and_header = 2 ij_java_keep_blank_lines_in_code = 2 ij_java_keep_blank_lines_in_declarations = 2 ij_java_keep_control_statement_in_one_line = true ij_java_keep_first_column_comment = true ij_java_keep_indents_on_empty_lines = false ij_java_keep_line_breaks = true ij_java_keep_multiple_expressions_in_one_line = false ij_java_keep_simple_blocks_in_one_line = false ij_java_keep_simple_classes_in_one_line = false ij_java_keep_simple_lambdas_in_one_line = false ij_java_keep_simple_methods_in_one_line = false ij_java_label_indent_absolute = false ij_java_label_indent_size = 0 ij_java_lambda_brace_style = next_line ij_java_layout_static_imports_separately = true ij_java_line_comment_add_space = false ij_java_line_comment_at_first_column = true ij_java_message_dd_suffix = EJB ij_java_message_eb_suffix = Bean ij_java_method_annotation_wrap = split_into_lines ij_java_method_brace_style = next_line ij_java_method_call_chain_wrap = off ij_java_method_parameters_new_line_after_left_paren = false ij_java_method_parameters_right_paren_on_new_line = false ij_java_method_parameters_wrap = off ij_java_modifier_list_wrap = false ij_java_names_count_to_use_import_on_demand = 99 ij_java_new_line_after_lparen_in_record_header = false ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* ij_java_parameter_annotation_wrap = off ij_java_parentheses_expression_new_line_after_left_paren = false ij_java_parentheses_expression_right_paren_on_new_line = false ij_java_place_assignment_sign_on_next_line = false ij_java_prefer_longer_names = true ij_java_prefer_parameters_wrap = false ij_java_record_components_wrap = normal ij_java_repeat_synchronized = true ij_java_replace_instanceof_and_cast = false ij_java_replace_null_check = true ij_java_replace_sum_lambda_with_method_ref = true ij_java_resource_list_new_line_after_left_paren = false ij_java_resource_list_right_paren_on_new_line = false ij_java_resource_list_wrap = off ij_java_rparen_on_new_line_in_record_header = false ij_java_session_dd_suffix = EJB ij_java_session_eb_suffix = Bean ij_java_session_hi_suffix = Home ij_java_session_lhi_prefix = Local ij_java_session_lhi_suffix = Home ij_java_session_li_prefix = Local ij_java_session_si_suffix = Service ij_java_space_after_closing_angle_bracket_in_type_argument = false ij_java_space_after_colon = true ij_java_space_after_comma = true ij_java_space_after_comma_in_type_arguments = true ij_java_space_after_for_semicolon = true ij_java_space_after_quest = true ij_java_space_after_type_cast = true ij_java_space_before_annotation_array_initializer_left_brace = false ij_java_space_before_annotation_parameter_list = false ij_java_space_before_array_initializer_left_brace = false ij_java_space_before_catch_keyword = true ij_java_space_before_catch_left_brace = true ij_java_space_before_catch_parentheses = true ij_java_space_before_class_left_brace = true ij_java_space_before_colon = true ij_java_space_before_colon_in_foreach = true ij_java_space_before_comma = false ij_java_space_before_do_left_brace = true ij_java_space_before_else_keyword = true ij_java_space_before_else_left_brace = true ij_java_space_before_finally_keyword = true ij_java_space_before_finally_left_brace = true ij_java_space_before_for_left_brace = true ij_java_space_before_for_parentheses = true ij_java_space_before_for_semicolon = false ij_java_space_before_if_left_brace = true ij_java_space_before_if_parentheses = true ij_java_space_before_method_call_parentheses = false ij_java_space_before_method_left_brace = true ij_java_space_before_method_parentheses = false ij_java_space_before_opening_angle_bracket_in_type_parameter = false ij_java_space_before_quest = true ij_java_space_before_switch_left_brace = true ij_java_space_before_switch_parentheses = true ij_java_space_before_synchronized_left_brace = true ij_java_space_before_synchronized_parentheses = true ij_java_space_before_try_left_brace = true ij_java_space_before_try_parentheses = true ij_java_space_before_type_parameter_list = false ij_java_space_before_while_keyword = true ij_java_space_before_while_left_brace = true ij_java_space_before_while_parentheses = true ij_java_space_inside_one_line_enum_braces = false ij_java_space_within_empty_array_initializer_braces = false ij_java_space_within_empty_method_call_parentheses = false ij_java_space_within_empty_method_parentheses = false ij_java_spaces_around_additive_operators = true ij_java_spaces_around_assignment_operators = true ij_java_spaces_around_bitwise_operators = true ij_java_spaces_around_equality_operators = true ij_java_spaces_around_lambda_arrow = true ij_java_spaces_around_logical_operators = true ij_java_spaces_around_method_ref_dbl_colon = false ij_java_spaces_around_multiplicative_operators = true ij_java_spaces_around_relational_operators = true ij_java_spaces_around_shift_operators = true ij_java_spaces_around_type_bounds_in_type_parameters = true ij_java_spaces_around_unary_operator = false ij_java_spaces_within_angle_brackets = false ij_java_spaces_within_annotation_parentheses = false ij_java_spaces_within_array_initializer_braces = false ij_java_spaces_within_braces = false ij_java_spaces_within_brackets = false ij_java_spaces_within_cast_parentheses = false ij_java_spaces_within_catch_parentheses = false ij_java_spaces_within_for_parentheses = false ij_java_spaces_within_if_parentheses = false ij_java_spaces_within_method_call_parentheses = false ij_java_spaces_within_method_parentheses = false ij_java_spaces_within_parentheses = false ij_java_spaces_within_switch_parentheses = false ij_java_spaces_within_synchronized_parentheses = false ij_java_spaces_within_try_parentheses = false ij_java_spaces_within_while_parentheses = false ij_java_special_else_if_treatment = true ij_java_subclass_name_suffix = Impl ij_java_ternary_operation_signs_on_next_line = false ij_java_ternary_operation_wrap = off ij_java_test_name_suffix = Test ij_java_throws_keyword_wrap = off ij_java_throws_list_wrap = off ij_java_use_external_annotations = false ij_java_use_fq_class_names = false ij_java_use_relative_indents = false ij_java_use_single_class_imports = true ij_java_variable_annotation_wrap = off ij_java_visibility = public ij_java_while_brace_force = always ij_java_while_on_new_line = true ij_java_wrap_comments = false ij_java_wrap_first_method_in_call_chain = false ij_java_wrap_long_lines = false [.editorconfig] ij_editorconfig_align_group_field_declarations = false ij_editorconfig_space_after_colon = false ij_editorconfig_space_after_comma = true ij_editorconfig_space_before_colon = false ij_editorconfig_space_before_comma = false ij_editorconfig_spaces_around_assignment_operators = true [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.qrc,*.rng,*.tld,*.wadl,*.wsdd,*.wsdl,*.xjb,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] ij_xml_align_attributes = true ij_xml_align_text = false ij_xml_attribute_wrap = normal ij_xml_block_comment_at_first_column = true ij_xml_keep_blank_lines = 2 ij_xml_keep_indents_on_empty_lines = false ij_xml_keep_line_breaks = true ij_xml_keep_line_breaks_in_text = true ij_xml_keep_whitespaces = false ij_xml_keep_whitespaces_around_cdata = preserve ij_xml_keep_whitespaces_inside_cdata = false ij_xml_line_comment_at_first_column = true ij_xml_space_after_tag_name = false ij_xml_space_around_equals_in_attribute = false ij_xml_space_inside_empty_tag = false ij_xml_text_wrap = normal [{*.bash,*.sh,*.zsh}] indent_size = 2 tab_width = 2 ij_shell_binary_ops_start_line = false ij_shell_keep_column_alignment_padding = false ij_shell_minify_program = false ij_shell_redirect_followed_by_space = false ij_shell_switch_cases_indented = false [{*.cjs,*.js}] ij_continuation_indent_size = 4 ij_javascript_align_imports = false ij_javascript_align_multiline_array_initializer_expression = false ij_javascript_align_multiline_binary_operation = false ij_javascript_align_multiline_chained_methods = false ij_javascript_align_multiline_extends_list = false ij_javascript_align_multiline_for = true ij_javascript_align_multiline_parameters = true ij_javascript_align_multiline_parameters_in_calls = false ij_javascript_align_multiline_ternary_operation = false ij_javascript_align_object_properties = 0 ij_javascript_align_union_types = false ij_javascript_align_var_statements = 0 ij_javascript_array_initializer_new_line_after_left_brace = false ij_javascript_array_initializer_right_brace_on_new_line = false ij_javascript_array_initializer_wrap = off ij_javascript_assignment_wrap = off ij_javascript_binary_operation_sign_on_next_line = false ij_javascript_binary_operation_wrap = off ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** ij_javascript_blank_lines_after_imports = 1 ij_javascript_blank_lines_around_class = 1 ij_javascript_blank_lines_around_field = 0 ij_javascript_blank_lines_around_function = 1 ij_javascript_blank_lines_around_method = 1 ij_javascript_block_brace_style = end_of_line ij_javascript_call_parameters_new_line_after_left_paren = false ij_javascript_call_parameters_right_paren_on_new_line = false ij_javascript_call_parameters_wrap = off ij_javascript_catch_on_new_line = false ij_javascript_chained_call_dot_on_new_line = true ij_javascript_class_brace_style = end_of_line ij_javascript_comma_on_new_line = false ij_javascript_do_while_brace_force = never ij_javascript_else_on_new_line = false ij_javascript_enforce_trailing_comma = keep ij_javascript_extends_keyword_wrap = off ij_javascript_extends_list_wrap = off ij_javascript_field_prefix = _ ij_javascript_file_name_style = relaxed ij_javascript_finally_on_new_line = false ij_javascript_for_brace_force = never ij_javascript_for_statement_new_line_after_left_paren = false ij_javascript_for_statement_right_paren_on_new_line = false ij_javascript_for_statement_wrap = off ij_javascript_force_quote_style = false ij_javascript_force_semicolon_style = false ij_javascript_function_expression_brace_style = end_of_line ij_javascript_if_brace_force = never ij_javascript_import_merge_members = global ij_javascript_import_prefer_absolute_path = global ij_javascript_import_sort_members = true ij_javascript_import_sort_module_name = false ij_javascript_import_use_node_resolution = true ij_javascript_imports_wrap = on_every_item ij_javascript_indent_case_from_switch = true ij_javascript_indent_chained_calls = true ij_javascript_indent_package_children = 0 ij_javascript_jsx_attribute_value = braces ij_javascript_keep_blank_lines_in_code = 2 ij_javascript_keep_first_column_comment = true ij_javascript_keep_indents_on_empty_lines = false ij_javascript_keep_line_breaks = true ij_javascript_keep_simple_blocks_in_one_line = false ij_javascript_keep_simple_methods_in_one_line = false ij_javascript_line_comment_add_space = true ij_javascript_line_comment_at_first_column = false ij_javascript_method_brace_style = end_of_line ij_javascript_method_call_chain_wrap = off ij_javascript_method_parameters_new_line_after_left_paren = false ij_javascript_method_parameters_right_paren_on_new_line = false ij_javascript_method_parameters_wrap = off ij_javascript_object_literal_wrap = on_every_item ij_javascript_parentheses_expression_new_line_after_left_paren = false ij_javascript_parentheses_expression_right_paren_on_new_line = false ij_javascript_place_assignment_sign_on_next_line = false ij_javascript_prefer_as_type_cast = false ij_javascript_prefer_explicit_types_function_expression_returns = false ij_javascript_prefer_explicit_types_function_returns = false ij_javascript_prefer_explicit_types_vars_fields = false ij_javascript_prefer_parameters_wrap = false ij_javascript_reformat_c_style_comments = false ij_javascript_space_after_colon = true ij_javascript_space_after_comma = true ij_javascript_space_after_dots_in_rest_parameter = false ij_javascript_space_after_generator_mult = true ij_javascript_space_after_property_colon = true ij_javascript_space_after_quest = true ij_javascript_space_after_type_colon = true ij_javascript_space_after_unary_not = false ij_javascript_space_before_async_arrow_lparen = true ij_javascript_space_before_catch_keyword = true ij_javascript_space_before_catch_left_brace = true ij_javascript_space_before_catch_parentheses = true ij_javascript_space_before_class_lbrace = true ij_javascript_space_before_class_left_brace = true ij_javascript_space_before_colon = true ij_javascript_space_before_comma = false ij_javascript_space_before_do_left_brace = true ij_javascript_space_before_else_keyword = true ij_javascript_space_before_else_left_brace = true ij_javascript_space_before_finally_keyword = true ij_javascript_space_before_finally_left_brace = true ij_javascript_space_before_for_left_brace = true ij_javascript_space_before_for_parentheses = true ij_javascript_space_before_for_semicolon = false ij_javascript_space_before_function_left_parenth = true ij_javascript_space_before_generator_mult = false ij_javascript_space_before_if_left_brace = true ij_javascript_space_before_if_parentheses = true ij_javascript_space_before_method_call_parentheses = false ij_javascript_space_before_method_left_brace = true ij_javascript_space_before_method_parentheses = false ij_javascript_space_before_property_colon = false ij_javascript_space_before_quest = true ij_javascript_space_before_switch_left_brace = true ij_javascript_space_before_switch_parentheses = true ij_javascript_space_before_try_left_brace = true ij_javascript_space_before_type_colon = false ij_javascript_space_before_unary_not = false ij_javascript_space_before_while_keyword = true ij_javascript_space_before_while_left_brace = true ij_javascript_space_before_while_parentheses = true ij_javascript_spaces_around_additive_operators = true ij_javascript_spaces_around_arrow_function_operator = true ij_javascript_spaces_around_assignment_operators = true ij_javascript_spaces_around_bitwise_operators = true ij_javascript_spaces_around_equality_operators = true ij_javascript_spaces_around_logical_operators = true ij_javascript_spaces_around_multiplicative_operators = true ij_javascript_spaces_around_relational_operators = true ij_javascript_spaces_around_shift_operators = true ij_javascript_spaces_around_unary_operator = false ij_javascript_spaces_within_array_initializer_brackets = false ij_javascript_spaces_within_brackets = false ij_javascript_spaces_within_catch_parentheses = false ij_javascript_spaces_within_for_parentheses = false ij_javascript_spaces_within_if_parentheses = false ij_javascript_spaces_within_imports = false ij_javascript_spaces_within_interpolation_expressions = false ij_javascript_spaces_within_method_call_parentheses = false ij_javascript_spaces_within_method_parentheses = false ij_javascript_spaces_within_object_literal_braces = false ij_javascript_spaces_within_object_type_braces = true ij_javascript_spaces_within_parentheses = false ij_javascript_spaces_within_switch_parentheses = false ij_javascript_spaces_within_type_assertion = false ij_javascript_spaces_within_union_types = true ij_javascript_spaces_within_while_parentheses = false ij_javascript_special_else_if_treatment = true ij_javascript_ternary_operation_signs_on_next_line = false ij_javascript_ternary_operation_wrap = off ij_javascript_union_types_wrap = on_every_item ij_javascript_use_chained_calls_group_indents = false ij_javascript_use_double_quotes = true ij_javascript_use_explicit_js_extension = global ij_javascript_use_path_mapping = always ij_javascript_use_public_modifier = false ij_javascript_use_semicolon_after_statement = true ij_javascript_var_declaration_wrap = normal ij_javascript_while_brace_force = never ij_javascript_while_on_new_line = false ij_javascript_wrap_comments = false [{*.gant,*.gradle,*.groovy,*.gy}] ij_groovy_align_group_field_declarations = false ij_groovy_align_multiline_array_initializer_expression = false ij_groovy_align_multiline_assignment = false ij_groovy_align_multiline_binary_operation = false ij_groovy_align_multiline_chained_methods = false ij_groovy_align_multiline_extends_list = false ij_groovy_align_multiline_for = true ij_groovy_align_multiline_list_or_map = true ij_groovy_align_multiline_method_parentheses = false ij_groovy_align_multiline_parameters = true ij_groovy_align_multiline_parameters_in_calls = false ij_groovy_align_multiline_resources = true ij_groovy_align_multiline_ternary_operation = false ij_groovy_align_multiline_throws_list = false ij_groovy_align_named_args_in_map = true ij_groovy_align_throws_keyword = false ij_groovy_array_initializer_new_line_after_left_brace = false ij_groovy_array_initializer_right_brace_on_new_line = false ij_groovy_array_initializer_wrap = off ij_groovy_assert_statement_wrap = off ij_groovy_assignment_wrap = off ij_groovy_binary_operation_wrap = off ij_groovy_blank_lines_after_class_header = 0 ij_groovy_blank_lines_after_imports = 1 ij_groovy_blank_lines_after_package = 1 ij_groovy_blank_lines_around_class = 1 ij_groovy_blank_lines_around_field = 0 ij_groovy_blank_lines_around_field_in_interface = 0 ij_groovy_blank_lines_around_method = 1 ij_groovy_blank_lines_around_method_in_interface = 1 ij_groovy_blank_lines_before_imports = 1 ij_groovy_blank_lines_before_method_body = 0 ij_groovy_blank_lines_before_package = 0 ij_groovy_block_brace_style = end_of_line ij_groovy_block_comment_at_first_column = true ij_groovy_call_parameters_new_line_after_left_paren = false ij_groovy_call_parameters_right_paren_on_new_line = false ij_groovy_call_parameters_wrap = off ij_groovy_catch_on_new_line = false ij_groovy_class_annotation_wrap = split_into_lines ij_groovy_class_brace_style = end_of_line ij_groovy_class_count_to_use_import_on_demand = 5 ij_groovy_do_while_brace_force = never ij_groovy_else_on_new_line = false ij_groovy_enum_constants_wrap = off ij_groovy_extends_keyword_wrap = off ij_groovy_extends_list_wrap = off ij_groovy_field_annotation_wrap = split_into_lines ij_groovy_finally_on_new_line = false ij_groovy_for_brace_force = never ij_groovy_for_statement_new_line_after_left_paren = false ij_groovy_for_statement_right_paren_on_new_line = false ij_groovy_for_statement_wrap = off ij_groovy_if_brace_force = never ij_groovy_import_annotation_wrap = 2 ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* ij_groovy_indent_case_from_switch = true ij_groovy_indent_label_blocks = true ij_groovy_insert_inner_class_imports = false ij_groovy_keep_blank_lines_before_right_brace = 2 ij_groovy_keep_blank_lines_in_code = 2 ij_groovy_keep_blank_lines_in_declarations = 2 ij_groovy_keep_control_statement_in_one_line = true ij_groovy_keep_first_column_comment = true ij_groovy_keep_indents_on_empty_lines = false ij_groovy_keep_line_breaks = true ij_groovy_keep_multiple_expressions_in_one_line = false ij_groovy_keep_simple_blocks_in_one_line = false ij_groovy_keep_simple_classes_in_one_line = true ij_groovy_keep_simple_lambdas_in_one_line = true ij_groovy_keep_simple_methods_in_one_line = true ij_groovy_label_indent_absolute = false ij_groovy_label_indent_size = 0 ij_groovy_lambda_brace_style = end_of_line ij_groovy_layout_static_imports_separately = true ij_groovy_line_comment_add_space = false ij_groovy_line_comment_at_first_column = true ij_groovy_method_annotation_wrap = split_into_lines ij_groovy_method_brace_style = end_of_line ij_groovy_method_call_chain_wrap = off ij_groovy_method_parameters_new_line_after_left_paren = false ij_groovy_method_parameters_right_paren_on_new_line = false ij_groovy_method_parameters_wrap = off ij_groovy_modifier_list_wrap = false ij_groovy_names_count_to_use_import_on_demand = 3 ij_groovy_parameter_annotation_wrap = off ij_groovy_parentheses_expression_new_line_after_left_paren = false ij_groovy_parentheses_expression_right_paren_on_new_line = false ij_groovy_prefer_parameters_wrap = false ij_groovy_resource_list_new_line_after_left_paren = false ij_groovy_resource_list_right_paren_on_new_line = false ij_groovy_resource_list_wrap = off ij_groovy_space_after_assert_separator = true ij_groovy_space_after_colon = true ij_groovy_space_after_comma = true ij_groovy_space_after_comma_in_type_arguments = true ij_groovy_space_after_for_semicolon = true ij_groovy_space_after_quest = true ij_groovy_space_after_type_cast = true ij_groovy_space_before_annotation_parameter_list = false ij_groovy_space_before_array_initializer_left_brace = false ij_groovy_space_before_assert_separator = false ij_groovy_space_before_catch_keyword = true ij_groovy_space_before_catch_left_brace = true ij_groovy_space_before_catch_parentheses = true ij_groovy_space_before_class_left_brace = true ij_groovy_space_before_closure_left_brace = true ij_groovy_space_before_colon = true ij_groovy_space_before_comma = false ij_groovy_space_before_do_left_brace = true ij_groovy_space_before_else_keyword = true ij_groovy_space_before_else_left_brace = true ij_groovy_space_before_finally_keyword = true ij_groovy_space_before_finally_left_brace = true ij_groovy_space_before_for_left_brace = true ij_groovy_space_before_for_parentheses = true ij_groovy_space_before_for_semicolon = false ij_groovy_space_before_if_left_brace = true ij_groovy_space_before_if_parentheses = true ij_groovy_space_before_method_call_parentheses = false ij_groovy_space_before_method_left_brace = true ij_groovy_space_before_method_parentheses = false ij_groovy_space_before_quest = true ij_groovy_space_before_switch_left_brace = true ij_groovy_space_before_switch_parentheses = true ij_groovy_space_before_synchronized_left_brace = true ij_groovy_space_before_synchronized_parentheses = true ij_groovy_space_before_try_left_brace = true ij_groovy_space_before_try_parentheses = true ij_groovy_space_before_while_keyword = true ij_groovy_space_before_while_left_brace = true ij_groovy_space_before_while_parentheses = true ij_groovy_space_in_named_argument = true ij_groovy_space_in_named_argument_before_colon = false ij_groovy_space_within_empty_array_initializer_braces = false ij_groovy_space_within_empty_method_call_parentheses = false ij_groovy_spaces_around_additive_operators = true ij_groovy_spaces_around_assignment_operators = true ij_groovy_spaces_around_bitwise_operators = true ij_groovy_spaces_around_equality_operators = true ij_groovy_spaces_around_lambda_arrow = true ij_groovy_spaces_around_logical_operators = true ij_groovy_spaces_around_multiplicative_operators = true ij_groovy_spaces_around_regex_operators = true ij_groovy_spaces_around_relational_operators = true ij_groovy_spaces_around_shift_operators = true ij_groovy_spaces_within_annotation_parentheses = false ij_groovy_spaces_within_array_initializer_braces = false ij_groovy_spaces_within_braces = true ij_groovy_spaces_within_brackets = false ij_groovy_spaces_within_cast_parentheses = false ij_groovy_spaces_within_catch_parentheses = false ij_groovy_spaces_within_for_parentheses = false ij_groovy_spaces_within_gstring_injection_braces = false ij_groovy_spaces_within_if_parentheses = false ij_groovy_spaces_within_list_or_map = false ij_groovy_spaces_within_method_call_parentheses = false ij_groovy_spaces_within_method_parentheses = false ij_groovy_spaces_within_parentheses = false ij_groovy_spaces_within_switch_parentheses = false ij_groovy_spaces_within_synchronized_parentheses = false ij_groovy_spaces_within_try_parentheses = false ij_groovy_spaces_within_tuple_expression = false ij_groovy_spaces_within_while_parentheses = false ij_groovy_special_else_if_treatment = true ij_groovy_ternary_operation_wrap = off ij_groovy_throws_keyword_wrap = off ij_groovy_throws_list_wrap = off ij_groovy_use_flying_geese_braces = false ij_groovy_use_fq_class_names = false ij_groovy_use_fq_class_names_in_javadoc = true ij_groovy_use_relative_indents = false ij_groovy_use_single_class_imports = true ij_groovy_variable_annotation_wrap = off ij_groovy_while_brace_force = never ij_groovy_while_on_new_line = false ij_groovy_wrap_long_lines = false [{*.htm,*.html,*.sht,*.shtm,*.shtml}] ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 ij_html_align_attributes = true ij_html_align_text = false ij_html_attribute_wrap = normal ij_html_block_comment_at_first_column = true ij_html_do_not_align_children_of_min_lines = 0 ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot ij_html_enforce_quotes = false ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var ij_html_keep_blank_lines = 2 ij_html_keep_indents_on_empty_lines = false ij_html_keep_line_breaks = true ij_html_keep_line_breaks_in_text = true ij_html_keep_whitespaces = false ij_html_keep_whitespaces_inside = span,pre,textarea ij_html_line_comment_at_first_column = true ij_html_new_line_after_last_attribute = never ij_html_new_line_before_first_attribute = never ij_html_quote_style = double ij_html_remove_new_line_before_tags = br ij_html_space_after_tag_name = false ij_html_space_around_equality_in_attribute = false ij_html_space_inside_empty_tag = false ij_html_text_wrap = normal ij_html_uniform_ident = false [{*.properties,spring.handlers,spring.schemas}] ij_properties_align_group_field_declarations = false ij_properties_keep_blank_lines = false ij_properties_key_value_delimiter = equals ij_properties_spaces_around_key_value_delimiter = false [{*.yaml,*.yml}] indent_size = 2 ij_yaml_keep_indents_on_empty_lines = false ij_yaml_keep_line_breaks = true ij_yaml_space_before_colon = true ij_yaml_spaces_within_braces = true ij_yaml_spaces_within_brackets = true ================================================ FILE: .githooks/pre-commit ================================================ #!/bin/sh set -e # stash any unstaged changes git stash push -m "prePush" -q --keep-index # unstash the unstashed changes (if any) on exit or interrupt function unstash { git stash apply stash^{/prePush} -q || true } trap unstash EXIT ./gradlew check test -q ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** A test replicating the bug will be more helpful than just a description of your issue. For example, this can be a JMH test showing a performance issue or a unit test showing a logic error. **Expected behavior** A clear and concise description of what you expected to happen. **Desktop (please complete the following information):** - OS: [e.g. Linux] - Version [e.g. 3.4.4] - JVM Version [e.g. Azul Zulu 11] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. ================================================ FILE: .github/workflows/asciidoc-build-only.yml ================================================ name: Generate Documentation from ASCIIDoc on: push: branches-ignore: [ master ] pull_request: branches: [ master ] jobs: checkout-and-deploy: runs-on: ubuntu-latest strategy: matrix: java: [ 11 ] steps: - name: Checkout uses: actions/checkout@master - name: Set up java uses: actions/setup-java@v2 with: java-version: ${{ matrix.java }} distribution: 'zulu' - name: Build ASCIIDoc with Gradle run: ./gradlew clean asciidoctor ================================================ FILE: .github/workflows/asciidoc.yml ================================================ name: Generate Github Pages from ASCIIDoc on: push: branches: [ master ] permissions: contents: write jobs: checkout-and-deploy: runs-on: ubuntu-latest strategy: matrix: java: [ 11 ] steps: - name: Checkout uses: actions/checkout@master - name: Set up java uses: actions/setup-java@v2 with: java-version: ${{ matrix.java }} distribution: 'zulu' - name: Build ASCIIDoc with Gradle run: ./gradlew clean asciidoctor - name: Simple deploy with git uses: JamesIves/github-pages-deploy-action@v4 with: folder: build/docs/asciidoc/en/ ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: [ master ] pull_request: branches: [ master ] schedule: - cron: '33 3 * * 3' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'java' ] steps: - name: Checkout repository uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} - name: Set up JDK 11 uses: actions/setup-java@v2 with: java-version: 11 distribution: 'zulu' - name: Autobuild uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 ================================================ FILE: .github/workflows/gradle-build.yml ================================================ name: Java CI with Gradle on: [ push, pull_request ] jobs: build: runs-on: ubuntu-latest strategy: matrix: java: [ 11, 17, 21 ] name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v2 - name: Set up java uses: actions/setup-java@v2 with: java-version: ${{ matrix.java }} distribution: 'zulu' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle run: ./gradlew build - name: Archive artifacts uses: actions/upload-artifact@v4 with: name: libs-and-reports-${{ matrix.java }} path: | build/libs build/reports ================================================ FILE: .github/workflows/gradle-wrapper-validation.yml ================================================ name: "Validate Gradle Wrapper" on: [push, pull_request] jobs: validation: name: "Validation" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: gradle/wrapper-validation-action@v1 ================================================ FILE: .github/workflows/jcstress-manual.yml ================================================ name: JCStress Testing - manual testing on: workflow_dispatch: inputs : mode : description : 'JCStress run mode: sanity, quick, default, tough, stress' required : true default : 'default' jobs: build: runs-on: ubuntu-latest strategy: matrix: java: [ 11, 17, 21 ] name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v2 - name: Set up java uses: actions/setup-java@v2 with: java-version: ${{ matrix.java }} distribution: 'zulu' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle run: ./gradlew jcstress -Pmode="${{ github.event.inputs.mode }}" - name: Archive artifacts uses: actions/upload-artifact@v4 with: name: jcstress-report-manual-${{ matrix.java }} path: build/reports/jcstress ================================================ FILE: .github/workflows/jcstress-quick.yml ================================================ name: JCStress Testing - commit-level quick check on: [ push, pull_request ] jobs: build: runs-on: ubuntu-latest strategy: matrix: java: [ 11, 17, 21 ] name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v2 - name: Set up java uses: actions/setup-java@v2 with: java-version: ${{ matrix.java }} distribution: 'zulu' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle run: ./gradlew jcstress -Pmode=quick - name: Archive artifacts uses: actions/upload-artifact@v4 with: name: jcstress-report-quick-${{ matrix.java }} path: build/reports/jcstress ================================================ FILE: .gitignore ================================================ /bin /build /.gradle /out .DS_Store *~ classes code templib SConstruct .*dblite pom.xml .project .classpath .settings .idea *.iml *.ipr *.iws *-gc.log gradle.properties src/jmh/generated_tests src/jcstress/generated_tests ================================================ FILE: .lgtm.yml ================================================ extraction: java: index: java_version: 11 ================================================ FILE: CHANGELOG.adoc ================================================ :Author: LMAX Development Team :Email: :Date: {docdata} include::./src/docs/asciidoc/en/changelog.adoc[] ================================================ FILE: LICENCE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.adoc ================================================ = LMAX Disruptor image:https://github.com/LMAX-Exchange/disruptor/workflows/Java%20CI%20with%20Gradle/badge.svg[Java CI with Gradle,link=https://github.com/LMAX-Exchange/disruptor/actions/workflows/gradle-build.yml] image:https://github.com/LMAX-Exchange/disruptor/workflows/CodeQL/badge.svg[CodeQL,link=https://github.com/LMAX-Exchange/disruptor/actions/workflows/codeql-analysis.yml] image:https://img.shields.io/github/license/LMAX-Exchange/disruptor[License,link=https://github.com/LMAX-Exchange/disruptor/blob/master/LICENCE.txt] A High Performance Inter-Thread Messaging Library == Maintainer LMAX Development Team == Support - Open a ticket in GitHub https://github.com/LMAX-Exchange/disruptor/issues[issue tracker] - https://groups.google.com/group/lmax-disruptor[Google Group] == Documentation * https://lmax-exchange.github.io/disruptor/[Overview] * https://lmax-exchange.github.io/disruptor/user-guide/index.html[User Guide] * https://lmax-exchange.github.io/disruptor/javadoc/com.lmax.disruptor/module-summary.html[API Documentation] * https://lmax-exchange.github.io/disruptor/developer-guide/index.html[Developer Guide] * https://github.com/LMAX-Exchange/disruptor/wiki/Frequently-Asked-Questions[FAQ] ================================================ FILE: build.gradle ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ plugins { id 'java-library' id 'maven-publish' id 'signing' id 'checkstyle' id 'idea' id "io.github.reyerizo.gradle.jcstress" version "0.8.15" id 'org.asciidoctor.jvm.convert' version '3.3.2' id "me.champeau.jmh" version "0.6.8" id "biz.aQute.bnd.builder" version "6.3.1" } group = 'com.lmax' version = new Version(major: 4, minor: 0, revision: 0, snapshot: true) defaultTasks 'build' ext { fullName = 'Disruptor Framework' fullDescription = 'Disruptor - Concurrent Programming Framework' teamName = 'LMAX Disruptor Development Team' siteUrl = 'https://lmax-exchange.github.io/disruptor' sourceUrl = 'git@github.com:LMAX-Exchange/disruptor.git' moduleName = 'com.lmax.disruptor' } apply from: 'gradle/maven.gradle' apply from: 'gradle/perf.gradle' apply from: 'gradle/jmh.gradle' apply from: 'gradle/asciidoc.gradle' apply from: 'gradle/jcstress.gradle' wrapper.gradleVersion = '7.6' repositories { mavenCentral() } sourceCompatibility = targetCompatibility = JavaVersion.VERSION_11 dependencies { checkstyle 'com.puppycrawl.tools:checkstyle:10.4' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' testImplementation 'org.hamcrest:hamcrest:2.2' } compileJava { options.fork = true options.debug = true options.warnings = true options.deprecation = true } compileTestJava { options.fork = true options.debug = true options.warnings = true options.deprecation = true } test { useJUnitPlatform() systemProperties = [ 'junit.jupiter.execution.parallel.enabled': 'true', 'junit.jupiter.execution.parallel.mode.default': 'concurrent' ] } javadoc { title = 'Disruptor' options.addStringOption('XDignore.symbol.file', '-quiet') options.author = true options.bottom = "Copyright © 2011 - ${Calendar.instance[Calendar.YEAR]} LMAX Ltd. All Rights Reserved." options.use = true options.version = true options.showFromPublic() } jar { manifest.attributes('Built-By': System.properties.get('user.name'), 'Automatic-Module-Name': moduleName) bnd( '-exportcontents': 'com.lmax.disruptor.*;-noimport:=true', '-noimportjava': 'true', 'Import-Package': '!*', 'Bundle-Name': fullName, 'Bundle-Vendor': teamName, 'Bundle-Description': fullDescription, 'Bundle-DocURL': siteUrl, 'Bundle-SymbolicName': moduleName, ) } tasks.withType(Test) { maxParallelForks = (int) Math.max(Math.floor(Runtime.runtime.availableProcessors() / 2), 1) } task setUpGitHooks(type: Exec, description: 'Add a pre-commit git hook that runs gradle check & test tasks') { def hooksFolder = file('.githooks').getAbsolutePath() commandLine 'git', 'config', 'core.hooksPath', hooksFolder } class Version { int major, minor = 0, revision = 0 boolean snapshot String stage String toString() { "$major.$minor.$revision${stage ? '.' + stage : ''}${snapshot ? '-SNAPSHOT' : ''}" } } sourceSets { examples { compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } } ================================================ FILE: config/checkstyle/checkstyle.xml ================================================ ================================================ FILE: config/checkstyle/suppress.xml ================================================ ================================================ FILE: gradle/asciidoc.gradle ================================================ // // Configure ASCIIDoc publishing // asciidoctor { languages 'en' resources { from(javadoc) { into './javadoc' } from('src/docs/resources') { into './resources' } from('src/docs/files') { into './files' } } // The attributes set here appear to _completely_ override whatever is set in // the documents themselves - so to some degree it's pointless setting anything // at a document level. That said, IntelliJ IDEA doesn't know this, so it's // still kind of nice to have them at a document level so it doesn't complain! attributes 'source-highlighter': 'rouge', 'toc': 'left', 'icons': 'font', 'xrefstyle': 'short', 'imagesdir': '.' copyResourcesOnlyIf 'html5' baseDirFollowsSourceFile() asciidoctorj.version = '2.4.3' } ================================================ FILE: gradle/jcstress.gradle ================================================ // // Configure JCStress // jcstress { jcstressDependency 'org.openjdk.jcstress:jcstress-core:0.11' includeTests = true mode = project.getProperties().getOrDefault("mode", "quick") } ================================================ FILE: gradle/jmh.gradle ================================================ // // Configure JMH benchmarks // sourceSets { jmh { compileClasspath += sourceSets.main.output + sourceSets.test.output runtimeClasspath += sourceSets.main.output + sourceSets.test.output } } def jmhLibVersion = '1.35' dependencies { jmhImplementation "org.openjdk.jmh:jmh-core:${jmhLibVersion}" jmhAnnotationProcessor "org.openjdk.jmh:jmh-generator-annprocess:${jmhLibVersion}" jmhImplementation 'net.openhft:affinity:3.23.2' } jmh { jmhVersion = jmhLibVersion jvmArgsPrepend = ['-Xms256m -Xmx256m -XX:MaxDirectMemorySize=1g'] resultsFile.set(project.file("${project.buildDir}/reports/jmh/result.json")) profilers = [ 'pauses' ] resultFormat = 'JSON' failOnError = true verbosity = 'NORMAL' includeTests = true } task jmhProfilers(type: JavaExec, description:'Lists the available profilers for the jmh task', group: 'jmh') { classpath = sourceSets.jmh.runtimeClasspath mainClass.set('org.openjdk.jmh.Main') args '-lprof' } ================================================ FILE: gradle/maven.gradle ================================================ // // Configure publishing to Maven // java { withJavadocJar() withSourcesJar() } publishing { publications { disruptor(MavenPublication) { from components.java pom { name = project.ext.fullName description = project.ext.fullDescription url = project.ext.siteUrl scm { url = "scm:${project.ext.sourceUrl}" connection = "scm:${project.ext.sourceUrl}" } licenses { license { name = 'The Apache License, Version 2.0' url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } developers { developer { id = 'team' name = teamName email = 'lmax-disruptor@googlegroups.com' } } } } } repositories { maven { url project.hasProperty('sonatypeUrl') ? project['sonatypeUrl'] : 'https://oss.sonatype.org/service/local/staging/deploy/maven2' credentials { username = project.hasProperty('sonatypeUsername') ? project['sonatypeUsername'] : 'fake-user' password = project.hasProperty('sonatypePassword') ? project['sonatypePassword'] : 'fake-password' } } } } signing { sign publishing.publications.disruptor } ================================================ FILE: gradle/perf.gradle ================================================ // // Configure setting up performance tests // sourceSets { perftest { compileClasspath += sourceSets.main.output + sourceSets.test.output runtimeClasspath += sourceSets.main.output + sourceSets.test.output } } dependencies { perftestCompileOnly 'org.hdrhistogram:HdrHistogram:2.1.12' } build.dependsOn perftestClasses task perfJar(type: Jar, group: 'build') { archiveAppendix.set('perf') from sourceSets.perftest.output from sourceSets.test.output from { configurations.perftestCompileOnly.collect { it.isDirectory() ? it : zipTree(it) } } with jar } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s ' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: settings.gradle ================================================ rootProject.name = 'disruptor' ================================================ FILE: src/docs/asciidoc/en/changelog.adoc ================================================ :Author: LMAX Development Team :Email: :Date: {docdata} = Changelog == 4.0.0 * Minimum Java version now 11 * Issue #323 - `WorkerPool` and `WorkProcessor` have been removed, no more `Disruptor::handleEventsWithWorkerPool` * `Disruptor` constructors using `Executor` have been removed. Use `ThreadFactory` instead. * Rolled up event handling extension interfaces on to `EventHandler`: ** `BatchStartAware` ** `LifecycleAware` ** `SequenceReportingEventHandler` * `FatalExceptionHandler` and `IgnoreExceptionHandler` now use the JDK 9 Platform Logging API, i.e. `System.Logger` * Add rewind batch feature to the `BatchEventProcessor` * Add a maximum batch size argument to `BatchEventProcessor` ** `EventHandler::onBatchStart` now gets both the `batchSize` as well as `queueDepth` (previously it had `batchSize` which reported queue depth) * Added documentation to `EventPoller` * `Util::log2` throws if passed a non-positive argument * Deprecations ** Deprecated `ThreadHints.onSpinWait()` ** Deprecated `Disruptor.handleExceptionsWith()` - this had been javadoc deprecated since 2015 but not in the code ** Removed previously deprecated methods *** `Ringbuffer.resetTo()` *** `ConsumerRepository.getLastSequenceInChain()` == 3.4.3 - Add Automatic-Module-Name to MANIFEST.MF == 3.4.2 - Fix race condition in BatchEventProcessor with 3 or more starting/halting concurrently. == 3.4.1 - Fix race between run() and halt() on BatchEventProcessor. == 3.4.0 - Drop support for JDK6, support JDK7 and above only. - Add `ThreadHints.onSpinWait` to all busy spins within Disruptor. - Increase default sleep time for LockSupport.parkNanos to prevent busy spinning. == 3.3.8 - Revert belt and braces WaitStrategy signalling. == 3.3.7 - Add batch size to `BatchStartAware.onBatchStart()` - Upgrade to newer versions of gradle, checkstyle and JUnit - Deprecate classes & methods for later release - Remove JMock and rewrite tests accordingly == 3.3.6 - Support adding gating sequences before calling Disruptor.start() - Fix minor concurrency race when dynamically adding sequences - Fix wrapping problem when adding work handlers to the Disruptor == 3.3.5 - Fix NPE in TimeoutBlockingWaitStrategy when used with WorkProcessor - Add LiteTimeoutBlockingWaitStrategy - Resignal any waiting threads when trying to publish to a full ring buffer == 3.3.4 - Small build fixes and refactorings - Removed unused MutableLong class == 3.3.3 - Support ThreadFactory in Disruptor DSL - Make use of the Executor deprecated == 3.3.2 - Minor Javadoc fixes, example code and file renames. - Additional generics for parametrised ExceptionHandler. == 3.3.1 - Minor Javadoc fixes, example code and file renames. - Make ExceptionHandler type parametrised. == 3.3.0 - Inheritance based Padding for RingBuffer and Sequencers. - Better DSL support for adding custom EventProcessors. - Remove deprecated methods (slightly breaking change) - Experimental LiteBlockingWaitStrategy - Experimental EventPoller for polling for data instead of waiting. == 3.2.1 Released (10-Mar-2014) - Minor build and IDE updates - Rewrite of performance tests to run without JUnit and separate Queues in to their own tests - Remove old throttled performance test - Add performance tests for immutable message example - Add performance tests for off-heap example - Remove old Caliper tests - Remove some stray yield() calls == 3.2.0 Released (13-Aug-2013) - Fix performance bug in WorkProcessor with MultiProducerSequencer - Add EventRelease support to WorkProcessor (experimental) - Add PingPongLatencyTest - Switch to HdrHistogram for latency measurement == 3.1.1 Released (9-Jul-2013) - Fix bug in WorkProcessor where consumers could get ahead of publishers == 3.1.0 Released (17-Jun-2013) - Fix bug in Disruptor DSL where some consumers wouldn't be included in the gating sequences. - Add support for using the EventTranslator with batch publication. - Support timeouts when shutting down the Disruptor using the DSL. == 3.0.1 Released (16-Apr-2013) - Remove Sequencer.ensureAvailable() and move functionality into the ProcessingSequenceBarrier. - Add get() method and deprecate getPublished() and getPreallocated() from the RingBuffer. - Add TimeoutException to SequenceBarrier.waitFor(). - Fix off by one bug in MultiProducerSequencer.publish(lo, hi). - Improve testing for Sequencers. == 3.0.0 Released (10-Apr-2013) - Add remaining capacity to RingBuffer - Add batch publish methods to Sequencer - Add DataProvider interface to decouple the RingBuffer and BatchEventProcessor - Upgrade to gradle 1.5 == 3.0.0.beta5 Released (08-Apr-2013) - Make Sequencer public == 3.0.0.beta4 Released (08-Apr-2013) - Refactoring, merge Publisher back into Sequencer and some of the gating sequence responsibilities up to the sequencer. == 3.0.0.beta3 Released (20-Feb-2013) - Significant Javadoc updates (thanks Jason Koch) - DSL support for WorkerPool - Small performance tweaks - Add TimeoutHandler and TimeoutBlockingWaitStrategy and support timeouts in BatchEventProcessor == 3.0.0.beta2 Released (7-Jan-2013) - Remove millisecond wakeup from BlockingWaitStrategy - Add RingBuffer.claimAndGetPreallocated - Add RingBuffer.isPublished == 3.0.0.beta1 Released (3-Jan-2013) - Remove claim strategies and replace with Publishers/Sequences, remove pluggability of claim strategies. - Introduce new multi-producer publisher algorithm (faster and more scalable). - Introduce more flexible EventPublisher interface that allow for static definition of translators that can handle local values. - Allow for dynamic addition of gating sequences to ring buffer. Default it to empty, will allow messages to be sent and the ring buffer to wrap if there are no gating sequences defined. - Remove batch writes to the ring buffer. - Remove timeout read methods. - Switch to gradle build and layout the source maven style. - API change, remove RingBuffer.get, add RingBuffer.getPreallocated for producers and RingBuffer.getPublished for consumers. - Change maven dependency group id to com.lmax. - Added PhasedBackoffStrategy. - Remove explicit claim/forcePublish and supply a resetTo method. - Added better handling of cases when the gating sequence is ahead of the cursor value. == 2.10.3 Released (22-Aug-2012) - Bug fix, race condition in SequenceGroup when removing Sequences and getting current value == 2.10.2 Released (21-Aug-2012) - Bug fix, potential race condition in BlockingWaitStrategy. - Bug fix set initial SequenceGroup value to -1 (Issue #27). - Deprecate timeout methods that will be removed in version 3. == 2.10.1 (6-June-2012) - Bug fix, correct OSGI metadata. - Remove unnecessary code in wait strategies. == 2.10 (13-May-2012) - Remove deprecated timeout methods. - Added OSGI metadata to jar file. - Removed PaddedAtomicLong and use Sequence in all places. - Fix various generics warnings. - Change Sequence implementation to work around IBM JDK bug and improve performance by ~10%. - Add a remainingCapacity() call to the Sequencer class. == 2.9 (8-Apr-2012) - Deprecate timeout methods for publishing. - Add tryNext and tryPublishEvent for events that shouldn't block during delivery. - Small performance enhancement for MultithreadClaimStrategy. == 2.8 (6-Feb-2012) - Create new MultithreadClaimStrategy that works between when threads are highly contended. Previous implementation is now called MultithreadLowContentionClaimStrategy - Fix for bug where EventProcessors weren't being added as gating sequences to the ring buffer. - Fix range tracking bug in Histogram == 2.7.1 (21-Dec-2011) - Artefacts made available via maven central repository. (groupId:com.googlecode.disruptor, artifactId:disruptor) See UsingDisruptorInYourProject for details. == 2.7 (12-Nov-2011) - Changed construction API to allow user supplied claim and wait strategies - Added AggregateEventHandler to support multiple EventHandlers from a single BatchEventProcessor - Support exception handling from LifecycleAware - Added timeout API calls for claiming a sequence when publishing - Use LockSupport.parkNanos() instead of Thread.sleep() to reduce latency - Reworked performance tests to better support profiling and use LinkedBlockingQueue for comparison because it performs better on the latest processors - Minor bugfixes == 2.6 - Introduced WorkerPool to allow the one time consumption of events by a worker in a pool of EventProcessors. - New internal implementation of SequenceGroup which is lock free at all times and garbage free for get and set operations. - SequenceBarrier now checks alert status on every call whether it is blocking or not. - Added scripts in preparation for publishing binaries to maven repository. == 2.5.1 - Bugfix for supporting SequenceReportingEventHandler from DSL. ([issue 9](https://github.com/LMAX-Exchange/disruptor/issues#issue/9)) - Bugfix for multi-threaded publishing to multiple ring buffers ([issue 10](https://github.com/LMAX-Exchange/disruptor/issues#issue/10)) - Change SequenceBarrier to always check alert status before entering waitFor cycle. Previously this was only checked when the requested sequence was not available. - Change ClaimStrategy to not spin when the buffer has no available capacity, instead go straight to yielding to allow event processors to catch up. == 2.5 - Changed RingBuffer and publisher API so any mutable object can be placed in the RingBuffer without having to extend AbstractEvent - Added EventPublisher implementation to allow the publishing steps to be combined into one action - DisruptorWizard has been renamed to Disruptor with added support for any subtype of EventProcessor - Introduced a new Sequencer class that allows the Disruptor to be applied to other data structures such as multiple arrays. This can be a very useful pattern when multiple event processors work on the same event and you want to avoid false sharing. The Diamond for FizzBuzz is a good example of the issue. It is also higher performance by avoiding the pointer indirection when arrays of primitives are used. - Further increased performance and scalability by reducing false sharing. - Added progressive backoff strategy to the MultiThreadedClaimStrategy to prevent publisher getting into the claim cycle when the buffer is full because of a slow EventProcessor. - Significantly improved performance to WaitStrategy.Option.BLOCKING - Introduced SequenceGroup to allow dynamic registration of EventProcessors. == 2.0.2 - Rework of "False Sharing" prevention which makes the performance much more predictable across all platforms. Special thanks to Jeff Hain for helping focus in on a solution. == 2.0.1 - Renaming mistake for publishEventAtSequence should have been claimEventAtSequence - Fixed bug in YieldingStrategy that was busy spinning more than yielding and introduced SleepingStrategy - Removed code duplication in Unicast perf tests for expected result == 2.0.0 - New API to reflect naming changes - Producer -> Publisher - Entry -> Event - Consumer -> EventProcessor - ConsumerBarrier -> DependencyBarrier - ProducerBarrier has been incorporated into the RingBuffer for ease of use - DisruptorWizard integrated for fluent API dependency graph construction - Rework of sequence tracking to avoid false sharing on Java 7, plus avoid mega-morphic calls to make better use of the instruction cache - Reduced usage of memory barriers where possible - WaitStrategy.YIELDING initially spins for a short period to reduce latency - Major performance improvement giving more than a 2X increase for throughput across most use cases. == 1.2.2 - ProducerBarrier change to yield after busy spinning for a while. This may help the situation when the the number of producers exceeds the number of cores. == 1.2.1 - Bug fix for setting the sequence in the ForceFillProducerBarrier. - Code syntax tidy up. == 1.2.0 - Bug fix for regression introduced inlining multi-thread producer commit tracking code. This was a critical bug for the multi-threaded producer scenario. - Added new ProducerBarrier method for claiming a batch of sequences. This feature can give a significant throughput increase. == 1.1.0 - Off by one regression bug in ProducerBarrier introduced in 1.0.9. - Clarified the algorithm for initial cursor value in the ClaimStrategy. == 1.0.9 - Added Apache 2.0 licence and comments. - Small performance improvements to producers barriers and BatchConsumer. == 1.0.8 - Bugfix for BatchConsumer sequence update when using SequenceTrackingHandler to ensure sequence is always updated at the end of a batch regardless. == 1.0.7 - Factored out LifecycleAware interface to allowing consumers handlers to be notified when their thread starts and shuts down. == 1.0.6 - Cache minimum consumer sequence in producer barriers. This helps make the performance more predictable on Nehalem processors and greater on earlier Core 2 processors. == 1.0.5 - Removed Entry interface. All Entries must now extend AbstractEntry. - Made setSequence package private on AbstractEntry for encapsulation. ================================================ FILE: src/docs/asciidoc/en/developer-guide/10_getting_and_building.adoc ================================================ = Getting & Building the Disruptor :Author: LMAX Development Team :Email: :Date: {docdata} A guide to checking out the code and building it == Getting Started 1. Check out the project locally to your machine + -- [source,shell script] ---- $ git clone git://github.com/LMAX-Exchange/disruptor.git $ cd disruptor ---- -- 2. Build a distribution + -- [source,shell script] ---- $ ./gradlew clean build ---- As a result of the build you should find the following files: - `disruptor/build/libs/disruptor-{VERSION_NUMBER}-SNAPSHOT.jar` - `disruptor/build/libs/disruptor-{VERSION_NUMBER}-SNAPSHOT-javadoc.jar` - `disruptor/build/libs/disruptor-{VERSION_NUMBER}-SNAPSHOT-sources.jar` -- 3. Run the tests + -- [source,shell script] ---- $ ./gradlew test ---- -- ================================================ FILE: src/docs/asciidoc/en/developer-guide/20_performance_tests.adoc ================================================ = Disruptor Performance Tests :Author: LMAX Development Team :Email: :Date: {docdata} == Run the performance tests When making changes to the Disruptor it is important to be aware of the performance impact of your change. To measure the performance characteristics of the Disruptor there are many tests in the `perftest` and `jmh` sourcesets. === Running Perf Tests The perf tests are all under `src/perftest` and are built using a custom framework. To run them you need to fist build the perf jar and then run a single test at a time, for example: [source,shell script] ---- $ ./gradlew perfJar $ java -cp build/libs/disruptor-perf-*.jar com.lmax.disruptor.sequenced.OneToOneSequencedThroughputTest ---- === Running JMH Tests There are also https://github.com/openjdk/jmh/[JMH Benchmarks] for testing the performance of the Disruptor, these can be run with a gradle command [source,shell script] ---- $ ./gradlew jmh ---- ==== Isolated CPU benchmarking Some JMH Benchmarks can be run on machines with isolated cpus to get results with less error. Build the `jmhJar` on your development machine and transfer it to your benchmarking machine with isolated cpus: [source,shell script] ---- dev-machine /path/to/disruptor $ ./gradlew jmhJar dev-machine /path/to/disruptor $ scp dev-machine:build/lib/disruptor-*-jmh.jar bench-machine: ---- Assuming a system set up with isolated cores, e.g. [source,shell script] ---- bench-machine ~ $ cat /proc/cmdline ... isolcpus=31,33,35,37,39,41,43,45,47,7,9,11,13,15,17,19,21,23 nohz_full=31,33,35,37,39,41,43,45,47,7,9,11,13,15,17,19,21,23 ... ---- And the system may have a `cpuset` setup to split application threads from sharing cpus with kernel code: [source,shell script] ---- bench-machine ~ $ cat /cpusets/app/cpus 5,7-23,29,31-47 ---- You can run the benchmarks taskset to some of those cpus so that Java threads (like GC and complication) run on some cores and JMH Benchmark threads are pinned to isolated cpus using the following command (which is running just the one benchmark, `MultiProducersSequencerBenchmark`): [source,shell script] ---- bench-machine ~ $ cat runBenchmarks.sh #!/bin/bash JAVA_HOME=/opt/jdk11/1.11.0.6_zulu11.37.17_ca-1 ISOLATED_CPUS=7,9,11,13,15,17,19,21,23 $JAVA_HOME/bin/java -jar ./disruptor-4.0.0-SNAPSHOT-jmh.jar -rf json -rff /tmp/jmh-result.json -foe true -v NORMAL -prof perf -jvmArgsPrepend -Xmx256m -jvmArgsPrepend -Xms256m -jvmArgsPrepend -XX:MaxDirectMemorySize=1g $@ bench-machine ~ $ sudo cset proc -v --exec app -- taskset -c 5,8,10,12,14,16,18,20,22,29,32,34,36,38,40,42,44,46 ./runBenchmarks.sh MultiProducersSequencerBenchmark ---- ================================================ FILE: src/docs/asciidoc/en/developer-guide/25_jsctress_tests.adoc ================================================ = jcstress Tests :Author: LMAX Development Team :Email: :Date: {docdata} https://github.com/openjdk/jcstress/ > The Java Concurrency Stress (jcstress) is the experimental harness and a suite of tests to aid the research in the correctness of concurrency support in the JVM, class libraries, and hardware. The Disruptor has some jcstress tests to experiment and validate the correctness of the concurrency promises that the Disruptor makes. == Running jcstress Tests [source,shell script] ---- $ ./gradlew jcstress ---- ================================================ FILE: src/docs/asciidoc/en/developer-guide/30_publishing_release.adoc ================================================ = Publishing Release :Author: LMAX Development Team :Email: :Date: {docdata} == Prerequisites 1. All tests should be completing successfully in CI and locally 2. The changelog is updated with relevant information 3. `build.gradle` has been updated with the version information for the proposed release, and pushed to GitHub 4. Create a new release via GitHub UI, this should tag the commit == GPG Key To sign the artifact, a requirement for public release, you must have a signing GPG key set up. === Creating a new GPG signing key If you already have a GPG signing key, you can skip to the next section. - If you are on version 2.1.17 or greater, run the command below to generate a GPG key pair: + -- [source,shell script] ---- $ gpg --full-generate-key ---- - At the prompt, specify the kind of key you want, or press Enter to accept the default `RSA and RSA`. - Enter the desired key size. Your key must be at least 4096 bits. - Enter the length of time the key should be valid. Press Enter to specify the default selection, indicating that the key doesn't expire. -- - If you are not on version 2.1.17 or greater, the `gpg --full-generate-key` command doesn't work. Use the following command instead: + -- [source,shell script] ---- $ gpg --default-new-key-algo rsa4096 --gen-key ---- -- - Verify that your selections are correct. - Enter your user ID information. - Type a secure passphrase. - (is using gpg >= 2.1) Export the keyring to a gpg file + -- [source,shell script] ---- gpg --keyring secring.gpg --export-secret-keys > ~/.gnupg/secring.gpg ---- -- === Adding signing options for gradle - `signing.keyId` is the last 8 characters of the public key id for the signing key, in this example, `2657EDD1`. - `signing.password` is the passphrase for your keyring - `signing.secretKeyRingFile` is the absolute path to the secret key ring file containing your private key [source,shell script] ---- $ gpg --list-secret-keys --keyid-format SHORT /home/dev/.gnupg/pubring.kbx ----------------------------- sec rsa4096/2657EDD1 2021-12-31 [SC] EA38CE5CFD74BBB831611491F9CE691A2657EDD1 uid [ultimate] LMAX Coders (LMAX Dev Team) ssb rsa4096/F4831415 2021-12-31 [E] $ cat gradle.properties signing.keyId=2657EDD1 signing.password= signing.secretKeyRingFile=/home/dev/.gnupg/secring.gpg ---- See: https://docs.gradle.org/current/userguide/signing_plugin.html === Publish your public key You need to publish your public key so that sonatype can verify who signed the release artifacts. ---- gpg --keyserver https://keyserver.ubuntu.com/ --send-keys 2657EDD1 ---- Note: Other `keyservers` are available == Release process 1. Clean any existing build data + -- [source,shell script] ---- $ ./gradlew clean ---- -- 2. Add `gradle.properties` file in the root of the disruptor project with the following content + -- [source,shell script] ---- $ cat gradle.properties sonatypeUsername= sonatypePassword= signing.keyId= signing.password=> signing.secretKeyRingFile=/home/dev/.gnupg/secring.gpg ---- -- 3. Build & upload to Sonatype + -- [source,shell script] ---- $ ./gradlew publish ---- *Note: Make sure the signing step was not skipped* -- 4. Release in to Sonatype + -- - Log in to https://oss.sonatype.org/ - Go to https://oss.sonatype.org/#stagingRepositories[Staging Repositories] - Verify all the files are present and correct (including `*.asc` files generated by signing) - Close the staging repository - Release the repository - Assuming all went well, wait to see changes at: https://repo1.maven.org/maven2/com/lmax/disruptor/ -- 5. Remove `gradle.properties` file + -- This file is ignored from git, but I would not advise leaving it on disk long term as it has 2 passwords in plain text. -- ================================================ FILE: src/docs/asciidoc/en/developer-guide/90_tips.adoc ================================================ = Development tips :Author: LMAX Development Team :Email: :Date: {docdata} == Git Hooks This project uses GitHub Actions for CI which runs the gradle build task to check the project can be built on each commit/pull request. So that you don't push changes that will fail the simple `test` & `check` steps we would suggest adding the git pre-commit hook which will block any commit you try to do locally if the code fails these steps. The git hook can be set up using the following gradle task: [source,shell script] ---- $ ./gradlew setUpGitHooks ---- == Signed Commits > Git is cryptographically secure, but it’s not foolproof. If you’re taking work from others on the internet and want to verify that commits are actually from a trusted source, Git has a few ways to sign and verify work using GPG. - https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work - https://docs.github.com/en/github/authenticating-to-github/signing-commits ================================================ FILE: src/docs/asciidoc/en/developer-guide/index.adoc ================================================ = LMAX Disruptor Developer Guide :Author: LMAX Development Team :Email: :Date: {docdata} :xrefstyle: short LMAX welcomes outside contributions to the Disruptor. The following documentation should help you get checked out, set up, and ready to start making changes to the codebase. // Leave some gaps between these includes (trust me) include::10_getting_and_building.adoc[leveloffset=+1] include::20_performance_tests.adoc[leveloffset=+1] include::25_jsctress_tests.adoc[leveloffset=+1] include::30_publishing_release.adoc[leveloffset=+1] include::90_tips.adoc[leveloffset=+1] ================================================ FILE: src/docs/asciidoc/en/disruptor.adoc ================================================ = LMAX Disruptor: High performance alternative to bounded queues for exchanging data between concurrent threads Martin Thompson; Dave Farley; Michael Barker; Patricia Gee; Andrew Stewart v1.0, May 2011 :sectnums: :toc: left :authors: Martin Thompson, Dave Farley, Michael Barker, Patricia Gee, Andrew Stewart :email: :date: 2011-05 :revnumber: 1.0 // If you're changing these, also check out asciidoctor.gradle! :xrefstyle: short :icons: font :gradle-rootdir: ../../../../ :imagesdir: ../../ https://github.com/LMAX-Exchange/disruptor .Abstract [Abstract] **** LMAX was established to create a very high performance financial exchange. As part of our work to accomplish this goal we have evaluated several approaches to the design of such a system, but as we began to measure these we ran into some fundamental limits with conventional approaches. Many applications depend on queues to exchange data between processing stages. Our performance testing showed that the latency costs, when using queues in this way, were in the same order of magnitude as the cost of IO operations to disk (RAID or SSD based disk system) – dramatically slow. If there are multiple queues in an end-to-end operation, this will add hundreds of microseconds to the overall latency. There is clearly room for optimisation. Further investigation and a focus on the computer science made us realise that the conflation of concerns inherent in conventional approaches, (e.g. queues and processing nodes) leads to contention in multi-threaded implementations, suggesting that there may be a better approach. Thinking about how modern CPUs work, something we like to call "`mechanical sympathy`", using good design practices with a strong focus on teasing apart the concerns, we came up with a data structure and a pattern of use that we have called the Disruptor. Testing has shown that the mean latency using the Disruptor for a three-stage pipeline is 3 orders of magnitude lower than an equivalent queue-based approach. In addition, the Disruptor handles approximately 8 times more throughput for the same configuration. These performance improvements represent a step change in the thinking around concurrent programming. This new pattern is an ideal foundation for any asynchronous event processing architecture where high-throughput and low-latency is required. At LMAX we have built an order matching engine, real-time risk management, and a highly available in-memory transaction processing system all on this pattern to great success. Each of these systems has set new performance standards that, as far as we can tell, are unsurpassed. However this is not a specialist solution that is only of relevance in the Finance industry. The Disruptor is a general-purpose mechanism that solves a complex problem in concurrent programming in a way that maximizes performance, and that is simple to implement. Although some of the concepts may seem unusual it has been our experience that systems built to this pattern are significantly simpler to implement than comparable mechanisms. The Disruptor has significantly less write contention, a lower concurrency overhead and is more cache friendly than comparable approaches, all of which results in greater throughput with less jitter at lower latency. On processors at moderate clock rates we have seen over 25 million messages per second and latencies lower than 50 nanoseconds. This performance is a significant improvement compared to any other implementation that we have seen. This is very close to the theoretical limit of a modern processor to exchange data between cores. **** == Overview The Disruptor is the result of our efforts to build the world’s highest performance financial exchange at LMAX. Early designs focused on architectures derived from SEDA footnote:SEDA[Staged Event-Driven Architecture – https://en.wikipedia.org/wiki/Staged_event-driven_architecture] and Actors footnote:actors[Actor model – http://dspace.mit.edu/handle/1721.1/6952] using pipelines for throughput. After profiling various implementations it became evident that the queuing of events between stages in the pipeline was dominating the costs. We found that queues also introduced latency and high levels of jitter. We expended significant effort on developing new queue implementations with better performance. However it became evident that queues as a fundamental data structure are limited due to the conflation of design concerns for the producers, consumers, and their data storage. The Disruptor is the result of our work to build a concurrent structure that cleanly separates these concerns. == The Complexities of Concurrency In the context of this document, and computer science in general, concurrency means not only that two or more tasks happen in parallel, but also that they contend on access to resources. The contended resource may be a database, file, socket or even a location in memory. Concurrent execution of code is about two things, mutual exclusion and visibility of change. Mutual exclusion is about managing contended updates to some resource. Visibility of change is about controlling when such changes are made visible to other threads. It is possible to avoid the need for mutual exclusion if you can eliminate the need for contended updates. If your algorithm can guarantee that any given resource is modified by only one thread, then mutual exclusion is unnecessary. Read and write operations require that all changes are made visible to other threads. However only contended write operations require the mutual exclusion of the changes. The most costly operation in any concurrent environment is a contended write access. To have multiple threads write to the same resource requires complex and expensive coordination. Typically this is achieved by employing a locking strategy of some kind. === The Cost of Locks Locks provide mutual exclusion and ensure that the visibility of change occurs in an ordered manner. Locks are incredibly expensive because they require arbitration when contended. This arbitration is achieved by a context switch to the operating system kernel which will suspend threads waiting on a lock until it is released. During such a context switch, as well as releasing control to the operating system which may decide to do other house-keeping tasks while it has control, execution context can lose previously cached data and instructions. This can have a serious performance impact on modern processors. Fast user mode locks can be employed but these are only of any real benefit when not contended. We will illustrate the cost of locks with a simple demonstration. The focus of this experiment is to call a function which increments a 64-bit counter in a loop 500 million times. This can be executed by a single thread on a 2.4Ghz Intel Westmere EP in just 300ms if written in Java. The language is unimportant to this experiment and results will be similar across all languages with the same basic primitives. Once a lock is introduced to provide mutual exclusion, even when the lock is as yet un-contended, the cost goes up significantly. The cost increases again, by orders of magnitude, when two or more threads begin to contend. The results of this simple experiment are shown in the table below: .Comparative costs of contention [cols2*,options="header"] |=== | Method | Time (ms) | Single thread | 300 | Single thread with lock | 10,000 | Two threads with lock | 224,000 | Single thread with CAS | 5,700 | Two threads with CAS | 30,000 | Single thread with volatile write | 4,700 |=== === The Costs of "`CAS`" A more efficient alternative to the use of locks can be employed for updating memory when the target of the update is a single word. These alternatives are based upon the atomic, or interlocked, instructions implemented in modern processors. These are commonly known as CAS (Compare And Swap) operations, e.g. "`lock cmpxchg`" on x86. A CAS operation is a special machine-code instruction that allows a word in memory to be conditionally set as an atomic operation. For the "`increment a counter experiment`" each thread can spin in a loop reading the counter then try to atomically set it to its new incremented value. The old and new values are provided as parameters to this instruction. If, when the operation is executed, the value of the counter matches the supplied expected value, the counter is updated with the new value. If, on the other hand, the value is not as expected, the CAS operation will fail. It is then up to the thread attempting to perform the change to retry, re-reading the counter incrementing from that value and so on until the change succeeds. This CAS approach is significantly more efficient than locks because it does not require a context switch to the kernel for arbitration. However CAS operations are not free of cost. The processor must lock its instruction pipeline to ensure atomicity and employ a memory barrier to make the changes visible to other threads. CAS operations are available in Java by using the `java.util.concurrent.Atomic*` classes. If the critical section of the program is more complex than a simple increment of a counter it may take a complex state machine using multiple CAS operations to orchestrate the contention. Developing concurrent programs using locks is difficult; developing lock-free algorithms using CAS operations and memory barriers is many times more complex and it is very difficult to prove that they are correct. The ideal algorithm would be one with only a single thread owning all writes to a single resource with other threads reading the results. To read the results in a multi-processor environment requires memory barriers to make the changes visible to threads running on other processors. === Memory Barriers Modern processors perform out-of-order execution of instructions and out-of-order loads and stores of data between memory and execution units for performance reasons. The processors need only guarantee that program logic produces the same results regardless of execution order. This is not an issue for single-threaded programs. However, when threads share state it is important that all memory changes appear in order, at the point required, for the data exchange to be successful. Memory barriers are used by processors to indicate sections of code where the ordering of memory updates is important. They are the means by which hardware ordering and visibility of change is achieved between threads. Compilers can put in place complimentary software barriers to ensure the ordering of compiled code, such software memory barriers are in addition to the hardware barriers used by the processors themselves. Modern CPUs are now much faster than the current generation of memory systems. To bridge this divide CPUs use complex cache systems which are effectively fast hardware hash tables without chaining. These caches are kept coherent with other processor cache systems via message passing protocols. In addition, processors have "`store buffers`" to offload writes to these caches, and "`invalidate queues`" so that the cache coherency protocols can acknowledge invalidation messages quickly for efficiency when a write is about to happen. What this means for data is that the latest version of any value could, at any stage after being written, be in a register, a store buffer, one of many layers of cache, or in main memory. If threads are to share this value, it needs to be made visible in an ordered fashion and this is achieved through the coordinated exchange of cache coherency messages. The timely generation of these messages can be controlled by memory barriers. A read memory barrier orders load instructions on the CPU that executes it by marking a point in the invalidate queue for changes coming into its cache. This gives it a consistent view of the world for write operations ordered before the read barrier. A write barrier orders store instructions on the CPU that executes it by marking a point in the store buffer, thus flushing writes out via its cache. This barrier gives an ordered view to the world of what store operations happen before the write barrier. A full memory barrier orders both loads and stores but only on the CPU that executes it. Some CPUs have more variants in addition to these three primitives but these three are sufficient to understand the complexities of what is involved. In the Java memory model the read and write of a volatile field implements the read and write barriers respectively. This was made explicit in the Java Memory Model footnote:jmm[Java Memory Model - https://jcp.org/en/jsr/detail?id=133] as defined with the release of Java 5. === Cache Lines The way in which caching is used in modern processors is of immense importance to successful high performance operation. Such processors are enormously efficient at churning through data and instructions held in cache and yet, comparatively, are massively inefficient when a cache miss occurs. Our hardware does not move memory around in bytes or words. For efficiency, caches are organised into cache-lines that are typically 32-256 bytes in size, the most common cache-line being 64 bytes. This is the level of granularity at which cache coherency protocols operate. This means that if two variables are in the same cache line, and they are written to by different threads, then they present the same problems of write contention as if they were a single variable. This is a concept know as "`false sharing`". For high performance then, it is important to ensure that independent, but concurrently written, variables do not share the same cache-line if contention is to be minimised. When accessing memory in a predictable manner CPUs are able to hide the latency cost of accessing main memory by predicting which memory is likely to be accessed next and pre-fetching it into the cache in the background. This only works if the processors can detect a pattern of access such as walking memory with a predictable "`stride`". When iterating over the contents of an array the stride is predictable and so memory will be pre-fetched in cache lines, maximizing the efficiency of the access. Strides typically have to be less than 2048 bytes in either direction to be noticed by the processor. However, data structures like linked lists and trees tend to have nodes that are more widely distributed in memory with no predictable stride of access. The lack of a consistent pattern in memory constrains the ability of the system to pre-fetch cache-lines, resulting in main memory accesses which can be more than 2 orders of magnitude less efficient. === The Problems of Queues Queues typically use either linked-lists or arrays for the underlying storage of elements. If an in-memory queue is allowed to be unbounded then for many classes of problem it can grow unchecked until it reaches the point of catastrophic failure by exhausting memory. This happens when producers outpace the consumers. Unbounded queues can be useful in systems where the producers are guaranteed not to outpace the consumers and memory is a precious resource, but there is always a risk if this assumption doesn’t hold and queue grows without limit. To avoid this catastrophic outcome, queues are commonly constrained in size (bounded). Keeping a queue bounded requires that it is either array-backed or that the size is actively tracked. Queue implementations tend to have write contention on the head, tail, and size variables. When in use, queues are typically always close to full or close to empty due to the differences in pace between consumers and producers. They very rarely operate in a balanced middle ground where the rate of production and consumption is evenly matched. This propensity to be always full or always empty results in high levels of contention and/or expensive cache coherence. The problem is that even when the head and tail mechanisms are separated using different concurrent objects such as locks or CAS variables, they generally occupy the same cache-line. The concerns of managing producers claiming the head of a queue, consumers claiming the tail, and the storage of nodes in between make the designs of concurrent implementations very complex to manage beyond using a single large-grain lock on the queue. Large grain locks on the whole queue for put and take operations are simple to implement but represent a significant bottleneck to throughput. If the concurrent concerns are teased apart within the semantics of a queue then the implementations become very complex for anything other than a single producer – single consumer implementation. In Java there is a further problem with the use of queues, as they are significant sources of garbage. Firstly, objects have to be allocated and placed in the queue. Secondly, if linked-list backed, objects have to be allocated representing the nodes of the list. When no longer referenced, all these objects allocated to support the queue implementation need to be re-claimed. === Pipelines and Graphs For many classes of problem it makes sense to wire together several processing stages into pipelines. Such pipelines often have parallel paths, being organised into graph-like topologies. The links between each stage are often implemented by queues with each stage having its own thread. This approach is not cheap - at each stage we have to incur the cost of en-queuing and de-queuing units of work. The number of targets multiplies this cost when the path must fork, and incurs an inevitable cost of contention when it must re-join after such a fork. It would be ideal if the graph of dependencies could be expressed without incurring the cost of putting the queues between stages. == Design of the LMAX Disruptor While trying to address the problems described above, a design emerged through a rigorous separation of the concerns that we saw as being conflated in queues. This approach was combined with a focus on ensuring that any data should be owned by only one thread for write access, therefore eliminating write contention. That design became known as the "`Disruptor`". It was so named because it had elements of similarity for dealing with graphs of dependencies to the concept of "`Phasers`" footnote:phasers[Phasers - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Phaser.html] in Java 7, introduced to support Fork-Join. The LMAX disruptor is designed to address all of the issues outlined above in an attempt to maximize the efficiency of memory allocation, and operate in a cache-friendly manner so that it will perform optimally on modern hardware. At the heart of the disruptor mechanism sits a pre-allocated bounded data structure in the form of a ring-buffer. Data is added to the ring buffer through one or more producers and processed by one or more consumers. === Memory Allocation All memory for the ring buffer is pre-allocated on start up. A ring-buffer can store either an array of pointers to entries or an array of structures representing the entries. The limitations of the Java language mean that entries are associated with the ring-buffer as pointers to objects. Each of these entries is typically not the data being passed itself, but a container for it. This pre-allocation of entries eliminates issues in languages that support garbage collection, since the entries will be re-used and live for the duration of the Disruptor instance. The memory for these entries is allocated at the same time and it is highly likely that it will be laid out contiguously in main memory and so support cache striding. There is a proposal by John Rose to introduce "`value types`" footnote:valuetypes[Value Types - https://blogs.oracle.com/jrose/tuples-in-the-vm] to the Java language which would allow arrays of tuples, like other languages such as C, and so ensure that memory would be allocated contiguously and avoid the pointer indirection. Garbage collection can be problematic when developing low-latency systems in a managed runtime environment like Java. The more memory that is allocated the greater the burden this puts on the garbage collector. Garbage collectors work at their best when objects are either very short-lived or effectively immortal. The pre-allocation of entries in the ring buffer means that it is immortal as far as garbage collector is concerned and so represents little burden. Under heavy load queue-based systems can back up, which can lead to a reduction in the rate of processing, and results in the allocated objects surviving longer than they should, thus being promoted beyond the young generation with generational garbage collectors. This has two implications: first, the objects have to be copied between generations which cause latency jitter; second, these objects have to be collected from the old generation which is typically a much more expensive operation and increases the likelihood of "`stop the world`" pauses that result when the fragmented memory space requires compaction. In large memory heaps this can cause pauses of seconds per GB in duration. === Teasing Apart the Concerns We saw the following concerns as being conflated in all queue implementations, to the extent that this collection of distinct behaviours tend to define the interfaces that queues implement: 1. Storage of items being exchanged 2. Coordination of producers claiming the next sequence for exchange 3. Coordination of consumers being notified that a new item is available When designing a financial exchange in a language that uses garbage collection, too much memory allocation can be problematic. So, as we have described linked-list backed queues are a not a good approach. Garbage collection is minimized if the entire storage for the exchange of data between processing stages can be pre-allocated. Further, if this allocation can be performed in a uniform chunk, then traversal of that data will be done in a manner that is very friendly to the caching strategies employed by modern processors. A data-structure that meets this requirement is an array with all the slots pre-filled. On creation of the ring buffer the Disruptor utilises the abstract factory pattern to pre-allocate the entries. When an entry is claimed, a producer can copy its data into the pre-allocated structure. On most processors there is a very high cost for the remainder calculation on the sequence number, which determines the slot in the ring. This cost can be greatly reduced by making the ring size a power of 2. A bit mask of size minus one can be used to perform the remainder operation efficiently. As we described earlier bounded queues suffer from contention at the head and tail of the queue. The ring buffer data structure is free from this contention and concurrency primitives because these concerns have been teased out into producer and consumer barriers through which the ring buffer must be accessed. The logic for these barriers is described below. In most common usages of the Disruptor there is usually only one producer. Typical producers are file readers or network listeners. In cases where there is a single producer there is no contention on sequence/entry allocation. In more unusual usages where there are multiple producers, producers will race one another to claim the next entry in the ring-buffer. Contention on claiming the next available entry can be managed with a simple CAS operation on the sequence number for that slot. Once a producer has copied the relevant data to the claimed entry it can make it public to consumers by committing the sequence. This can be done without CAS by a simple busy spin until the other producers have reached this sequence in their own commit. Then this producer can advance the cursor signifying the next available entry for consumption. Producers can avoid wrapping the ring by tracking the sequence of consumers as a simple read operation before they write to the ring buffer. Consumers wait for a sequence to become available in the ring buffer before they read the entry. Various strategies can be employed while waiting. If CPU resource is precious they can wait on a condition variable within a lock that gets signalled by the producers. This obviously is a point of contention and only to be used when CPU resource is more important than latency or throughput. The consumers can also loop checking the cursor which represents the currently available sequence in the ring buffer. This could be done with or without a thread yield by trading CPU resource against latency. This scales very well as we have broken the contended dependency between the producers and consumers if we do not use a lock and condition variable. Lock free multi-producer – multi-consumer queues do exist but they require multiple CAS operations on the head, tail, size counters. The Disruptor does not suffer this CAS contention. === Sequencing Sequencing is the core concept to how the concurrency is managed in the Disruptor. Each producer and consumer works off a strict sequencing concept for how it interacts with the ring buffer. Producers claim the next slot in sequence when claiming an entry in the ring. This sequence of the next available slot can be a simple counter in the case of only one producer or an atomic counter updated using CAS operations in the case of multiple producers. Once a sequence value is claimed, this entry in the ring buffer is now available to be written to by the claiming producer. When the producer has finished updating the entry it can commit the changes by updating a separate counter which represents the cursor on the ring buffer for the latest entry available to consumers. The ring buffer cursor can be read and written in a busy spin by the producers using memory barrier without requiring a CAS operation as below. [source,java] ---- long expectedSequence = claimedSequence – 1; while (cursor != expectedSequence) { // busy spin } cursor = claimedSequence; ---- Consumers wait for a given sequence to become available by using a memory barrier to read the cursor. Once the cursor has been updated the memory barriers ensure the changes to the entries in the ring buffer are visible to the consumers who have waited on the cursor advancing. Consumers each contain their own sequence which they update as they process entries from the ring buffer. These consumer sequences allow the producers to track consumers to prevent the ring from wrapping. Consumer sequences also allow consumers to coordinate work on the same entry in an ordered manner In the case of having only one producer, and regardless of the complexity of the consumer graph, no locks or CAS operations are required. The whole concurrency coordination can be achieved with just memory barriers on the discussed sequences. === Batching Effect When consumers are waiting on an advancing cursor sequence in the ring buffer an interesting opportunity arises that is not possible with queues. If the consumer finds the ring buffer cursor has advanced a number of steps since it last checked it can process up to that sequence without getting involved in the concurrency mechanisms. This results in the lagging consumer quickly regaining pace with the producers when the producers burst ahead thus balancing the system. This type of batching increases throughput while reducing and smoothing latency at the same time. Based on our observations, this effect results in a close to constant time for latency regardless of load, up until the memory sub-system is saturated, and then the profile is linear following Little’s Law footnote:littleslaw[Little’s Law - https://en.wikipedia.org/wiki/Little%27s_law]. This is very different to the "`J`" curve effect on latency we have observed with queues as load increases. === Dependency Graphs A queue represents the simple one step pipeline dependency between producers and consumers. If the consumers form a chain or graph-like structure of dependencies then queues are required between each stage of the graph. This incurs the fixed costs of queues many times within the graph of dependent stages. When designing the LMAX financial exchange our profiling showed that taking a queue based approach resulted in queuing costs dominating the total execution costs for processing a transaction. Because the producer and consumer concerns are separated with the Disruptor pattern, it is possible to represent a complex graph of dependencies between consumers while only using a single ring buffer at the core. This results in greatly reduced fixed costs of execution thus increasing throughput while reducing latency. A single ring buffer can be used to store entries with a complex structure representing the whole workflow in a cohesive place. Care must be taken in the design of such a structure so that the state written by independent consumers does not result in false sharing of cache lines. === Disruptor Class Diagram The core relationships in the Disruptor framework are depicted in the class diagram below. This diagram leaves out the convenience classes which can be used to simplify the programming model. After the dependency graph is constructed the programming model is simple. Producers claim entries in sequence via a `ProducerBarrier`, write their changes into the claimed entry, then commit that entry back via the `ProducerBarrier` making them available for consumption. As a consumer all one needs do is provide a `BatchHandler` implementation that receives call backs when a new entry is available. This resulting programming model is event based having a lot of similarities to the Actor Model. Separating the concerns normally conflated in queue implementations allows for a more flexible design. A `RingBuffer` exists at the core of the Disruptor pattern providing storage for data exchange without contention. The concurrency concerns are separated out for the producers and consumers interacting with the `RingBuffer`. The `ProducerBarrier` manages any concurrency concerns associated with claiming slots in the ring buffer, while tracking dependant consumers to prevent the ring from wrapping. The `ConsumerBarrier` notifies consumers when new entries are available, and Consumers can be constructed into a graph of dependencies representing multiple stages in a processing pipeline. image::./resources/images/classdiagram.png[] === Code Example The code below is an example of a single producer and single consumer using the convenience interface `BatchHandler` for implementing a consumer. The consumer runs on a separate thread receiving entries as they become available. [source,java] ---- // Callback handler which can be implemented by consumers final BatchHandler batchHandler = new BatchHandler() { public void onAvailable(final ValueEntry entry) throws Exception { // process a new entry as it becomes available. } public void onEndOfBatch() throws Exception { // useful for flushing results to an IO device if necessary. } public void onCompletion() { // do any necessary clean up before shutdown } }; RingBuffer ringBuffer = new RingBuffer(ValueEntry.ENTRY_FACTORY, SIZE, ClaimStrategy.Option.SINGLE_THREADED, WaitStrategy.Option.YIELDING); ConsumerBarrier consumerBarrier = ringBuffer.createConsumerBarrier(); BatchConsumer batchConsumer = new BatchConsumer(consumerBarrier, batchHandler); ProducerBarrier producerBarrier = ringBuffer.createProducerBarrier(batchConsumer); // Each consumer can run on a separate thread EXECUTOR.submit(batchConsumer); // Producers claim entries in sequence ValueEntry entry = producerBarrier.nextEntry(); // copy data into the entry container // make the entry available to consumers producerBarrier.commit(entry); ---- == Throughput Performance Testing As a reference we choose Doug Lea’s excellent ``java.util.concurrent.ArrayBlockingQueue``footnote:arrayblockingqueue[ArrayBlockingQueue - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ArrayBlockingQueue.html] which has the highest performance of any bounded queue based on our testing. The tests are conducted in a blocking programming style to match that of the Disruptor. The tests cases detailed below are available in the Disruptor open source project. WARNING: running the tests requires a system capable of executing at least 4 threads in parallel. .Unicast: 1P – 1C image::./resources/images/unicast1p1c.png[] .Three Step Pipeline: 1P – 3C image::./resources/images/threestep1p3c.png[] .Sequencer: 3P – 1C image::./resources/images/sequencer3p1c.png[] .Multicast: 1P – 3C image::./resources/images/multicast1p3c.png[] .Diamond: 1P – 3C image::./resources/images/diamond1p3c.png[] For the above configurations an `ArrayBlockingQueue` was applied for each arc of data flow compared to barrier configuration with the Disruptor. The following table shows the performance results in operations per second using a Java 1.6.0_25 64-bit Sun JVM, Windows 7, Intel Core i7 860 @ 2.8 GHz without HT and Intel Core i7-2720QM, Ubuntu 11.04, and taking the best of 3 runs when processing 500 million messages. Results can vary substantially across different JVM executions and the figures below are not the highest we have observed. .Comparative throughput (in ops per sec) [cols=5*,options="header"] |=== | 2+| Nehalem 2.8Ghz – Windows 7 SP1 64-bit 2+| Sandy Bridge 2.2Ghz – Linux 2.6.38 64-bit | h| ABQ h| Disruptor h| ABQ h| Disruptor | Unicast: 1P – 1C | 5,339,256 | 25,998,336 | 4,057,453 | 22,381,378 | Pipeline: 1P – 3C | 2,128,918 | 16,806,157 | 2,006,903 | 15,857,913 | Sequencer: 3P – 1C | 5,539,531 | 13,403,268 | 2,056,118 | 14,540,519 | Multicast: 1P – 3C | 1,077,384 | 9,377,871 | 260,733 | 10,860,121 | Diamond: 1P – 3C | 2,113,941 | 16,143,613 | 2,082,725 | 15,295,197 |=== .Comparative throughput updated for modern hardware (in ops per sec) [cols=4*,options="header"] |=== | 3+| AMD EPYC 9374F – Linux 5.4.277 – OpenJDK 11.0.24 | h| ABQ h| Disruptor 3 h| Disruptor 4 | Unicast: 1P – 1C | 20,895,148 | 134,553,283 | 160,359,204 | Pipeline: 1P – 3C | 5,216,647 | 76,068,766 | 101,317,122 | Sequencer: 3P – 1C | 18,791,340 | 16,010,759 | 29,726,516 | Multicast: 1P – 3C | 2,355,379 | 68,157,033 | 70,018,204 | Diamond: 1P – 3C | 3,433,665 | 61,229,488 | 63,123,343 |=== == Latency Performance Testing To measure latency we take the three stage pipeline and generate events at less than saturation. This is achieved by waiting 1 microsecond after injecting an event before injecting the next and repeating 50 million times. To time at this level of precision it is necessary to use time stamp counters from the CPU. We chose CPUs with an invariant TSC because older processors suffer from changing frequency due to power saving and sleep states. Intel Nehalem and later processors use an invariant TSC which can be accessed by the latest Oracle JVMs running on Ubuntu 11.04. No CPU binding has been employed for this test. For comparison we use the ArrayBlockingQueue once again. We could have used ConcurrentLinkedQueue footnote:CLQ[ConcurrentLinkedQueue - http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html] which is likely to give better results but we want to use a bounded queue implementation to ensure producers do not outpace consumers by creating back pressure. The results below are for 2.2Ghz Core i7-2720QM running Java 1.6.0_25 64-bit on Ubuntu 11.04. Mean latency per hop for the Disruptor comes out at 52 nanoseconds compared to 32,757 nanoseconds for ArrayBlockingQueue. Profiling shows the use of locks and signalling via a condition variable are the main cause of latency for the ArrayBlockingQueue. .Comparative Latency in three stage pipeline [cols=3*,options="header"] |=== | | Array Blocking Queue (ns) | Disruptor (ns) | Min Latency | 145 | 29 | Mean Latency | 32,757 | 52 | 99% observations less than | 2,097,152 | 128 | 99.99% observations less than | 4,194,304 | 8,192 | Max Latency | 5,069,086 | 175,567 |=== == Conclusion The Disruptor is a major step forward for increasing throughput, reducing latency between concurrent execution contexts and ensuring predictable latency, an important consideration in many applications. Our testing shows that it out-performs comparable approaches for exchanging data between threads. We believe that this is the highest performance mechanism for such data exchange. By concentrating on a clean separation of the concerns involved in cross-thread data exchange, by eliminating write contention, minimizing read contention and ensuring that the code worked well with the caching employed by modern processors, we have created a highly efficient mechanism for exchanging data between threads in any application. The batching effect that allows consumers to process entries up to a given threshold, without any contention, introduces a new characteristic in high performance systems. For most systems, as load and contention increase there is an exponential increase in latency, the characteristic "`J`" curve. As load increases on the Disruptor, latency remains almost flat until saturation occurs of the memory sub-system. We believe that the Disruptor establishes a new benchmark for high-performance computing and is very well placed to continue to take advantage of current trends in processor and computer design. View the original PDF of this paper link:./files/Disruptor-1.0.pdf[here]. ================================================ FILE: src/docs/asciidoc/en/index.adoc ================================================ = LMAX Disruptor High Performance Inter-Thread Messaging Library :Author: LMAX Development Team :Email: :Date: {docdata} // If you're changing these, also check out asciidoctor.gradle! :xrefstyle: short :icons: font :gradle-rootdir: ../../../../ :imagesdir: ../../ == Read This First To understand the problem the Disruptor is trying to solve, and to get a feel for why this concurrency framework is so fast, read the <>. It also contains detailed performance results. And now for some words from our sponsors... LMAX are recruiting once again. If you are interested in working with a great team, with some amazing technology, and think you can add something to the mix then please check out our https://careers.lmax.com/[jobs page]. == What is the Disruptor? https://www.lmax.com[LMAX] aims to be the fastest trading platform in the world. Clearly, in order to achieve this we needed to do something special to achieve very low-latency and high-throughput with our Java platform. Performance testing showed that using queues to pass data between stages of the system was introducing latency, so we focused on optimising this area. The Disruptor is the result of our research and testing. We found that cache misses at the CPU-level, and locks requiring kernel arbitration are both extremely costly, so we created a framework which has "mechanical sympathy" for the hardware it's running on, and that's lock-free. This is not a specialist solution, it's not designed to work only for a financial application. The Disruptor is a general-purpose mechanism for solving a difficult problem in concurrent programming. It works in a different way to more conventional approaches, so you use it a little differently than you might be used to. For example, applying the pattern to your system is not as simple as replacing all your queues with the https://trishagee.com/2011/06/22/dissecting_the_disruptor_whats_so_special_about_a_ring_buffer/[magic ring buffer]. We've got: - a <> to guide you, - a growing number of https://github.com/LMAX-Exchange/disruptor/wiki/Blogs-And-Articles[blogs and articles] giving an overview of how it works, - the <> goes into some detail as you'd expect, - and the performance tests give examples of how to use the Disruptor. If you prefer real, live people explaining things instead of a dry paper or content-heavy website, there's always the https://www.infoq.com/presentations/LMAX/[presentation Mike and Martin gave] at QCon San Francisco. If you fancy a natter with the folks involved head over to our https://groups.google.com/g/lmax-disruptor[Discussion Group]. Martin Thompson will also witter on occasionally about performance in his https://mechanical-sympathy.blogspot.com/[Mechanical Sympathy blog]. Martin Fowler has also done a great https://martinfowler.com/articles/lmax.html[review] of the Disruptor's application at LMAX. == What's the big deal? It's fast. Very fast. .Latency histogram comparing Disruptor to ArrayBlockingQueue image::resources/images/latency-histogram.png[] Note that this is a log-log scale, not linear. If we tried to plot the comparisons on a linear scale, we'd run out of space very quickly. We have performance results of the test that produced these results, plus others of throughput testing. == Great What do I do next? - Read the <>, - Read the <<./javadoc/index#,API documentation>>, - Check out our https://github.com/LMAX-Exchange/disruptor/wiki/Frequently-Asked-Questions[Frequently Asked Questions] - Want to work on the Disruptor? Read the <>, == Discussion, Blogs & Other Useful Links - https://groups.google.com/g/lmax-disruptor[Disruptor Google Group] - <> - https://martinfowler.com/articles/lmax.html[Martin Fowler's Technical Review] - https://github.com/disruptor-net/Disruptor-net[.NET Disruptor Port] - https://www.lmax.com[LMAX Exchange] - https://www.lmax.com/blog/staff-blogs/[LMAX Staff Blogs] - https://mechanical-sympathy.blogspot.com/[Mechanical Sympathy] (Martin Thompson) - https://bad-concurrency.blogspot.com/[Bad Concurrency] (Michael Barker) == Presentations - https://www.infoq.com/presentations/LMAX/[Disruptor presentation @ QCon SF] - https://www.slideshare.net/trishagee/introduction-to-the-disruptor[Introduction to the Disruptor] == Changelog The changelog can be <>. ================================================ FILE: src/docs/asciidoc/en/user-guide/10_using_the_disruptor.adoc ================================================ = Using the Disruptor :Author: LMAX Development Team :Email: :Date: {docdata} // If you're changing these, also check out asciidoctor.gradle! :xrefstyle: short :icons: font :gradle-rootdir: ../../../../../ :imagesdir: ../../ == Introduction The Disruptor is a library that provides a concurrent ring buffer data structure. It is designed to provide a low-latency, high-throughput work queue in asynchronous event processing architectures. To understand the benefits of the Disruptor we can compare it to something well understood and quite similar in purpose. In the case of the Disruptor this would be Java's `BlockingQueue`. Like a queue the purpose of the Disruptor is to move data (e.g. messages or events) between threads within the same process. However, there are some key features that the Disruptor provides that distinguish it from a queue. They are: - Multicast events to consumers, with <<_consumer_dependency_graph,consumer dependency graph>>. - <<_event_pre_allocation, Pre-allocate memory>> for events. - <<_optionally_lock_free, Optionally lock-free>>. === Core Concepts Before we can understand how the Disruptor works, it is worthwhile defining a number of terms that will be used throughout the documentation and the code. For those with a DDD bent, think of this as the ubiquitous language of the Disruptor domain. - **Ring Buffer**: The Ring Buffer is often considered the main aspect of the Disruptor. However, from 3.0 onwards, the Ring Buffer is only responsible for the storing and updating of the data (``Event``s) that move through the Disruptor. For some advanced use cases, it can even be completely replaced by the user. - *Sequence*: The Disruptor uses ``Sequence``s as a means to identify where a particular component is up to. Each consumer (Event Processor) maintains a `Sequence` as does the Disruptor itself. The majority of the concurrent code relies on the movement of these Sequence values, hence the `Sequence` supports many of the current features of an `AtomicLong`. In fact the only real difference between the two is that the `Sequence` contains additional functionality to prevent false sharing between ``Sequence``s and other values. - *Sequencer*: The Sequencer is the real core of the Disruptor. The two implementations (single producer, multi producer) of this interface implement all the concurrent algorithms for fast, correct passing of data between producers and consumers. - *Sequence Barrier*: The Sequencer produces a Sequence Barrier that contains references to the main published `Sequence` from the Sequencer and the ``Sequence``s of any dependent consumer. It contains the logic to determine if there are any events available for the consumer to process. - *Wait Strategy*: The Wait Strategy determines how a consumer will wait for events to be placed into the Disruptor by a producer. More details are available in the section about being optionally lock-free. - *`Event`*: The unit of data passed from producer to consumer. There is no specific code representation of the Event as it defined entirely by the user. - *Event Processor*: The main event loop for handling events from the Disruptor and has ownership of consumer's Sequence. There is a single representation called BatchEventProcessor that contains an efficient implementation of the event loop and will call back onto a user supplied implementation of the EventHandler interface. - *Event Handler*: An interface that is implemented by the user and represents a consumer for the Disruptor. - *Producer*: This is the user code that calls the Disruptor to enqueue ``Event``s. This concept also has no representation in the code. To put these elements into context, below is an example of how LMAX uses the Disruptor within its high performance core services, e.g. the exchange. [#user-guide-models] .Disruptor with a set of dependent consumers. image::../resources/images/user-guide/models.png[] === Multicast Events This is the biggest behavioural difference between queues and the Disruptor. When you have multiple consumers listening on the same Disruptor, it publishes all events to all consumers. In contrast, a queue will only send a single event to a single consumer. You can use this behaviour of the Disruptor when you need to independent multiple parallel operations on the same data. .Example use-case [example] **** The canonical example from LMAX is where we have three operations: - journalling (writing the input data to a persistent journal file); - replication (sending the input data to another machine to ensure that there is a remote copy of the data); - and business logic (the real processing work). Looking at <> is possible to see that there are 3 ``EventHandler``s listening (`JournalConsumer`, `ReplicationConsumer` and `ApplicationConsumer`) to the Disruptor. Each of these Event Handlers will receive *all* the messages available in the Disruptor (in the same order). This allows for work for each of these consumers to operate in parallel. **** [#_consumer_dependency_graph] === Consumer Dependency Graph To support real world applications of the parallel processing behaviour it was necessary to support co-ordination between the consumers. Referring back to the example described above, it is necessary to prevent the business logic consumer from making progress until the journalling and replication consumers have completed their tasks. We call this concept "`gating`" (or, more correctly, the feature is a form of "`gating`"). "`Gating`" happens in two places: - Firstly we need to ensure that the producers do not overrun consumers. This is handled by adding the relevant consumers to the Disruptor by calling `RingBuffer.addGatingConsumers()`. - Secondly, the case referred to previously is implemented by constructing a SequenceBarrier containing Sequences from the components that must complete their processing first. Referring to <> there are 3 consumers listening for Events from the Ring Buffer. There is a dependency graph in this example. The ApplicationConsumer depends on the `JournalConsumer` and `ReplicationConsumer`. This means that the `JournalConsumer` and `ReplicationConsumer` can run freely in parallel with each other. The dependency relationship can be seen by the connection from the ``ApplicationConsumer``'s `SequenceBarrier` to the ``Sequence``s of the `JournalConsumer` and `ReplicationConsumer`. It is also worth noting the relationship that the `Sequencer` has with the downstream consumers. One of its roles is to ensure that publication does not wrap the Ring Buffer. To do this none of the downstream consumer may have a `Sequence` that is lower than the Ring Buffer's `Sequence` less the size of the Ring Buffer. However, by using the graph of dependencies an interesting optimisation can be made. Because the ``ApplicationConsumer``'s Sequence is guaranteed to be less than or equal to that of the `JournalConsumer` and `ReplicationConsumer` (that is what that dependency relationship ensures) the `Sequencer` need only look at the Sequence of the `ApplicationConsumer`. In a more general sense the `Sequencer` only needs to be aware of the ``Sequence``s of the consumers that are the leaf nodes in the dependency tree. [#_event_pre_allocation] === Event Pre-allocation One of the goals of the Disruptor is to enable use within a low latency environment. Within low-latency systems it is necessary to reduce or remove memory allocations. In Java-based system the purpose is to reduce the number stalls due to garbage collection footnote:low-latency[in low-latency C/C++ systems,heavy memory allocation is also problematic due to the contention that be placed on the memory allocator]. To support this the user is able to preallocate the storage required for the events within the Disruptor. During construction and `EventFactory` is supplied by the user and will be called for each entry in the Disruptor's Ring Buffer. When publishing new data to the Disruptor the API will allow the user to get hold of the constructed object so that they can call methods or update fields on that store object. The Disruptor provides guarantees that these operations will be concurrency-safe as long as they are implemented correctly. [#_optionally_lock_free] === Optionally Lock-free Another key implementation detail pushed by the desire for low-latency is the extensive use of lock-free algorithms to implement the Disruptor. All memory visibility and correctness guarantees are implemented using memory barriers and/or compare-and-swap operations. [CAUTION] -- There is only one use-case where an actual lock is required and that is within the `BlockingWaitStrategy`. This is done solely for the purpose of using a *condition* so that a consuming thread can be parked while waiting for new events to arrive. Many low-latency systems will use a busy-wait to avoid the jitter that can be incurred by using a *condition*; however, in number of system busy-wait operations can lead to significant degradation in performance, especially where the CPU resources are heavily constrained, e.g. web servers in virtualised-environments. -- == Getting Started === Getting the Disruptor The Disruptor jar file is available from https://search.maven.org/artifact/com.lmax/disruptor[Maven Central] and can be integrated into your dependency manager of choice from there. === Basic Produce and Consume To get started with the Disruptor we are going to consider very simple and contrived example. We will pass a single `long` value from a producer to a consumer, and the consumer will simply print out the value. There are currently several styles of writing publishers and consumers for the Disruptor. Whilst they are all fundamentally similar, there may be slight nuances in each approach that will be covered below. Firstly we will define the `Event` that will carry the data and is common to all following examples: .Example `LongEvent` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/longevent/LongEvent.java[tag=example] ---- In order to allow the Disruptor to preallocate these events for us, we need to an `EventFactory` that will perform the construction. This could be a method reference, such as `LongEvent::new` or an explicit implementation of the `EventFactory` interface: .Example `LongEventFactory` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/longevent/LongEventFactory.java[tag=example] ---- Once we have the event defined, we need to create a consumer that will handle these events. As an example, we will create an `EventHandler` that will print the value out to the console. .Example `LongEventHandler` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/longevent/LongEventHandler.java[tag=example] ---- Finally, we will need a source for these events. For simplicity, we will assume that the data is coming from some sort of I/O device, e.g. network or file in the form of a `ByteBuffer`. ==== Publishing [[publishing-using-lambdas]] ===== Using Lambdas Since version 3.0 of the Disruptor it has been possible to use a Lambda-style API to write publishers. This is the preferred approach, as it encapsulates much of the complexity of the alternatives. .Publishing events using lambdas [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/longevent/lambdas/LongEventMain.java[tag=example] ---- <1> Specify the size of the ring buffer, must be power of 2. <2> Construct the Disruptor <3> Connect the handler <4> Start the Disruptor, starts all threads running <5> Get the ring buffer from the Disruptor to be used for publishing. [WARNING] -- Notice that the lambda used for `publishEvent()` only refers to the parameters that are passed in. If we were to instead write that code as: .Example of a capturing lambda [source,java] ---- ByteBuffer bb = ByteBuffer.allocate(8); for (long l = 0; true; l++) { bb.putLong(0, l); ringBuffer.publishEvent((event, sequence) -> event.set(bb.getLong(0))); Thread.sleep(1000); } ---- This would create a capturing lambda, meaning that it would need to instantiate an object to hold the `ByteBuffer bb` variable as it passes the lambda through to the `publishEvent()` call. This will create additional (unnecessary) garbage, so the call that passes the argument through to the lambda should be preferred if low GC pressure is a requirement. -- Given that method references can be used instead of anonymous lambdas, it is possible to rewrite the example in this fashion: .Example using method references [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/longevent/methodrefs/LongEventMain.java[tag=example] ---- ===== Using Translators Prior to version 3.0, the preferred way of publishing messages was via the Event Publisher/Event Translator interfaces: .Example `LongEventProducer` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/longevent/LongEventProducer.java[tag=example] ---- This approach uses number of extra classes (e.g. handler, translator) that are not explicitly required when using lambdas. The advantage of here is that the translator code can be pulled into a separate class and easily unit tested. The Disruptor provides a number of different interfaces (`EventTranslator`, `EventTranslatorOneArg`, `EventTranslatorTwoArg`, etc.) that can be implemented to provide translators. This is to allow for the translators to be represented as static classes and lambdas (see <>). The arguments to the translation method are passed through the call on the Ring Buffer through to the translator. ==== Publishing Using the Legacy API There is a more "raw" approach that we can use: .Example Legacy `LongEventProducer` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/longevent/legacy/LongEventProducer.java[tag=example] ---- <1> Grab the next sequence <2> Get the entry in the Disruptor for that sequence <3> Fill the entry with data What becomes immediately obvious is that event publication becomes more involved than using a simple queue. This is due to the desire for Event pre-allocation. It requires (at the lowest level) a 2-phase approach to message publication, i.e. claim the slot in the ring buffer and then publish the available data. [CAUTION] -- It is also necessary to wrap publication in a `try`/`finally` block. If we claim a slot in the Ring Buffer (calling ``RingBuffer#next()``) then we must publish this sequence. Failing to do so can result in corruption of the state of the Disruptor. Specifically, in the multi-producer case, this will result in the consumers stalling and being unable to recover without a restart. Therefore, it is recommended that either the lambda or `EventTranslator` APIs be used. -- The final step is to wire the whole thing together. Whilst it is possible to wire up each component manually, this can be complicated and so a DSL is provided to simplify construction. TIP: Some of the more complicated options are not available via the DSL; however, it is suitable for most circumstances. .Example using the legacy `LongEventProducer` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/longevent/legacy/LongEventMain.java[tag=example] ---- === Basic Tuning Options Using the above approach will work functionally in the widest set of deployment scenarios. However, there are a number of tuning options that can be used to improve performance. The two main options for tuning are: - <<_single_vs_multiple_producers, Single vs. multiple producers>>, _and_ - <<_alternative_wait_strategies, Alternative wait strategies>>. Both of these options are set when constructing a Disruptor: .Tuning the Disruptor [source,java] ---- public class LongEventMain { public static void main(final String[] args) { //..... Disruptor disruptor = new Disruptor( factory, bufferSize, DaemonThreadFactory.INSTANCE, ProducerType.SINGLE, // <1> new BlockingWaitStrategy() // <2> ); //..... } } ---- <1> Use `ProducerType#SINGLE` to create a `SingleProducerSequencer`; use `ProducerType#MULTI` to create a `MultiProducerSequence` <2> Set the desired `WaitStrategy` [#_single_vs_multiple_producers] ==== Single vs. Multiple Producers One of the best ways to improve performance in concurrent systems is to adhere to the https://mechanical-sympathy.blogspot.com/2011/09/single-writer-principle.html[Single Writer Principle], this applies to the Disruptor. If you are in the situation where there will only ever be a single thread producing events into the Disruptor, then you can take advantage of this to gain additional performance. To give an indication of how much of a performance advantage can be achieved through this technique we can change the producer type in the OneToOne performance test. Tests run on i7 Sandy Bridge MacBook Air. .Multiple Producer [%autowidth] |=== | Run 0 | Disruptor=26,553,372 ops/sec | Run 1 | Disruptor=28,727,377 ops/sec | Run 2 | Disruptor=29,806,259 ops/sec | Run 3 | Disruptor=29,717,682 ops/sec | Run 4 | Disruptor=28,818,443 ops/sec | Run 5 | Disruptor=29,103,608 ops/sec | Run 6 | Disruptor=29,239,766 ops/sec |=== .Single Producer [%autowidth] |=== | Run 0 | Disruptor=89,365,504 ops/sec | Run 1 | Disruptor=77,579,519 ops/sec | Run 2 | Disruptor=78,678,206 ops/sec | Run 3 | Disruptor=80,840,743 ops/sec | Run 4 | Disruptor=81,037,277 ops/sec | Run 5 | Disruptor=81,168,831 ops/sec | Run 6 | Disruptor=81,699,346 ops/sec |=== [#_alternative_wait_strategies] ==== Alternative Wait Strategies The default `WaitStrategy` used by the Disruptor is the `BlockingWaitStrategy`. Internally the `BlockingWaitStrategy` uses a typical lock and condition variable to handle thread wake-up. The `BlockingWaitStrategy` is the slowest of the available wait strategies, but is the most conservative with the respect to CPU usage and will give the most consistent behaviour across the widest variety of deployment options. Knowledge of the deployed system can allow for additional performance by choosing a more appropriate wait strategy: - **SleepingWaitStrategy** -> -- Like the `BlockingWaitStrategy` the `SleepingWaitStrategy` it attempts to be conservative with CPU usage by using a simple busy wait loop. The difference is that the `SleepingWaitStrategy` uses a call to link:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/LockSupport.html#parkNanos(long)[`LockSupport.parkNanos(1)`^] in the middle of the loop. On a typical Linux system this will pause the thread for around 60µs. This has the benefits that the producing thread does not need to take any action other increment the appropriate counter and that it does not require the cost of signalling a condition variable. However, the mean latency of moving the event between the producer and consumer threads will be higher. It works best in situations where low latency is not required, but a low impact on the producing thread is desired. A common use case is for asynchronous logging. -- - **YieldingWaitStrategy** -> -- The `YieldingWaitStrategy` is one of two ``WaitStrategy``s that can be use in low-latency systems. It is designed for cases where there is the option to burn CPU cycles with the goal of improving latency. The `YieldingWaitStrategy` will busy spin, waiting for the sequence to increment to the appropriate value. Inside the body of the loop `Thread#yield()` will be called allowing other queued threads to run. This is the recommended wait strategy when you need very high performance, and the number of `EventHandler` threads is lower than the total number of logical cores, e.g. you have hyper-threading enabled. -- - **BusySpinWaitStrategy** -> -- The `BusySpinWaitStrategy` is the highest performing `WaitStrategy`. Like the `YieldingWaitStrategy`, it can be used in low-latency systems, but puts the highest constraints on the deployment environment. This wait strategy should only be used if the number of `EventHandler` threads is lower than the number of physical cores on the box, e.g. hyper-threading should be disabled. -- === Clearing Objects From the Ring Buffer When passing data via the Disruptor, it is possible for objects to live longer than intended. To avoid this happening it may be necessary to clear out the event after processing it. If you have a single event handler, clearing out the value within the same handler is sufficient. If you have a chain of event handlers, then you may need a specific handler placed at the end of the chain to handle clearing out the object. .Example `ObjectEvent` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/objectevent/ObjectEvent.java[tag=example] ---- .Example `ClearingEventHandler` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/objectevent/ClearingEventHandler.java[tag=example] ---- <1> Failing to call `clear()` here will result in the object associated with the event to live until it is overwritten. This will only happen once the ring buffer has wrapped around to the beginning. .Example using the `ClearingEventHandler` [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/objectevent/Main.java[tag=example] ---- == Advanced Techniques === Dealing With Large Batches .Example of "Early Release" [source,java] ---- include::{gradle-rootdir}/src/examples/java/com/lmax/disruptor/examples/EarlyReleaseHandler.java[tag=example] ---- ================================================ FILE: src/docs/asciidoc/en/user-guide/20_design_and_implementation.adoc ================================================ = Design and Implementation :Author: LMAX Development Team :Email: :Date: {docdata} // If you're changing these, also check out asciidoctor.gradle! :xrefstyle: short :icons: font - Single Producer Algorithm - Multiple Producer Algorithm ================================================ FILE: src/docs/asciidoc/en/user-guide/30_known_issues.adoc ================================================ = Known Issues :Author: LMAX Development Team :Email: :Date: {docdata} // If you're changing these, also check out asciidoctor.gradle! :xrefstyle: short :icons: font - On 32 bit Linux systems it appears that `LockSupport.parkNanos()` is quite expensive, therefore using the `SleepingWaitStrategy` is not recommended. ================================================ FILE: src/docs/asciidoc/en/user-guide/40_batch_rewind_use_case.adoc ================================================ = Batch Rewind :Author: LMAX Development Team :Email: :Date: {docdata} == The Feature When using the `BatchEventProcessor` to handle events as batches, there is a feature available that can be used to recover from an exception named "Batch Rewind". If something goes wrong while handling an event that is recoverable, the user can throw a `RewindableException`. This will invoke the `BatchRewindStrategy` instead of the usual `ExceptionHandler` to decide whether the sequence number should rewind back to the beginning of the batch to be reattempted or rethrow and delegate to the `ExceptionHandler`. e.g. When using the `SimpleBatchRewindStrategy` (which will always rewind) then the `BatchEventProcessor` receives a batch from 150 -> 155, but a temporary failure happens on sequence 153 (which throws a `RewindableException`). The events processed will look like the following... ``` 150, 151, 152, 153(failed -> rewind), 150, 151, 152, 153(succeeded this time), 154, 155 ``` The default `BatchRewindStrategy` is the `SimpleBatchRewindStrategy` but different strategies can be provided to the `BatchEventProcessor` like so... ``` batchEventProcessor.setRewindStrategy(batchRewindStrategy); ``` == Use Case This can be very useful when batches are handled as database transactions. So the start of a batch starts a transaction, events are handled as statements, and only committed at the end of a batch. Happy case ``` Batch start -> START TRANSACTION; Event 1 -> insert a row; Event 2 -> insert a row; Event 3 -> insert a row; Batch end -> COMMIT; ``` Sad case without Batch Rewind ``` Batch start -> START TRANSACTION; Event 1 -> insert a row; Event 2 -> DATABASE has a blip and can not commit Throw error -> ROLLBACK; User needs to explcitily reattempt the batch or choose to abandon the batch ``` Sad case with Batch Rewind ``` Batch start -> START TRANSACTION; Event 1 -> insert a row; Event 2 -> DATABASE has a blip and can not insert Throw RewindableException -> ROLLBACK; Batch start -> START TRANSACTION; Event 1 -> insert a row; Event 2 -> insert a row; Event 3 -> insert a row; Batch end -> COMMIT; ``` ================================================ FILE: src/docs/asciidoc/en/user-guide/index.adoc ================================================ = LMAX Disruptor User Guide :Author: LMAX Development Team :Email: :Date: {docdata} // If you're changing these, also check out asciidoctor.gradle! :xrefstyle: short :icons: font :gradle-rootdir: ../../../../../ :imagesdir: ../../ The LMAX Disruptor is a high performance inter-thread messaging library. It grew out of LMAX's research into concurrency, performance and non-blocking algorithms and today forms a core part of their Exchange's infrastructure. // Leave some gaps between these includes (trust me) include::10_using_the_disruptor.adoc[leveloffset=+1] include::20_design_and_implementation.adoc[leveloffset=+1] include::30_known_issues.adoc[leveloffset=+1] include::40_batch_rewind_use_case.adoc[leveloffset=+1] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/DynamicallyAddHandler.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.examples.support.StubEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class DynamicallyAddHandler { private static class DynamicHandler implements EventHandler { private final CountDownLatch shutdownLatch = new CountDownLatch(1); @Override public void onEvent(final StubEvent event, final long sequence, final boolean endOfBatch) { } @Override public void onStart() { } @Override public void onShutdown() { shutdownLatch.countDown(); } public void awaitShutdown() throws InterruptedException { shutdownLatch.await(); } } public static void main(final String[] args) throws InterruptedException { ExecutorService executor = Executors.newCachedThreadPool(DaemonThreadFactory.INSTANCE); // Build a disruptor and start it. Disruptor disruptor = new Disruptor<>( StubEvent.EVENT_FACTORY, 1024, DaemonThreadFactory.INSTANCE); RingBuffer ringBuffer = disruptor.start(); // Construct 2 batch event processors. DynamicHandler handler1 = new DynamicHandler(); BatchEventProcessor processor1 = new BatchEventProcessorBuilder().build(ringBuffer, ringBuffer.newBarrier(), handler1); DynamicHandler handler2 = new DynamicHandler(); BatchEventProcessor processor2 = new BatchEventProcessorBuilder().build(ringBuffer, ringBuffer.newBarrier(processor1.getSequence()), handler2); // Dynamically add both sequences to the ring buffer ringBuffer.addGatingSequences(processor1.getSequence(), processor2.getSequence()); // Start the new batch processors. executor.execute(processor1); executor.execute(processor2); // Remove a processor. // Stop the processor processor2.halt(); // Wait for shutdown the complete handler2.awaitShutdown(); // Remove the gating sequence from the ring buffer ringBuffer.removeGatingSequence(processor2.getSequence()); } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/EarlyReleaseHandler.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.examples.support.LongEvent; @SuppressWarnings("unused") // tag::example[] public class EarlyReleaseHandler implements EventHandler { private Sequence sequenceCallback; private int batchRemaining = 20; @Override public void setSequenceCallback(final Sequence sequenceCallback) { this.sequenceCallback = sequenceCallback; } @Override public void onEvent(final LongEvent event, final long sequence, final boolean endOfBatch) { processEvent(event); boolean logicalChunkOfWorkComplete = isLogicalChunkOfWorkComplete(); if (logicalChunkOfWorkComplete) { sequenceCallback.set(sequence); } batchRemaining = logicalChunkOfWorkComplete || endOfBatch ? 20 : batchRemaining; } private boolean isLogicalChunkOfWorkComplete() { // Ret true or false based on whatever criteria is required for the smaller // chunk. If this is doing I/O, it may be after flushing/syncing to disk // or at the end of DB batch+commit. // Or it could simply be working off a smaller batch size. return --batchRemaining == -1; } private void processEvent(final LongEvent event) { // Do processing } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/HandleExceptionOnTranslate.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.EventTranslator; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.examples.support.LongEvent; import com.lmax.disruptor.util.DaemonThreadFactory; public class HandleExceptionOnTranslate { private static final int NO_VALUE_SPECIFIED = -1; private static class MyHandler implements EventHandler { @Override public void onEvent(final LongEvent event, final long sequence, final boolean endOfBatch) { if (event.get() == NO_VALUE_SPECIFIED) { System.out.printf("Discarded%n"); } else { System.out.printf("Processed: %s%n", event.get() == sequence); } } } public static void main(final String[] args) throws InterruptedException { Disruptor disruptor = new Disruptor<>(LongEvent.FACTORY, 1024, DaemonThreadFactory.INSTANCE); disruptor.handleEventsWith(new MyHandler()); disruptor.start(); EventTranslator t = (event, sequence) -> { event.set(NO_VALUE_SPECIFIED); if (sequence % 3 == 0) { throw new RuntimeException("Skipping"); } event.set(sequence); }; for (int i = 0; i < 10; i++) { try { disruptor.publishEvent(t); } catch (RuntimeException e) { // Skipping } } Thread.sleep(5000); } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/KeyedBatching.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventHandler; import java.util.ArrayList; import java.util.List; public class KeyedBatching implements EventHandler { private static final int MAX_BATCH_SIZE = 100; private final List batch = new ArrayList<>(); private long key = 0; @Override public void onEvent(final KeyedEvent event, final long sequence, final boolean endOfBatch) { if (!batch.isEmpty() && event.key != key) { processBatch(batch); } batch.add(event.data); key = event.key; if (endOfBatch || batch.size() >= MAX_BATCH_SIZE) { processBatch(batch); } } private void processBatch(final List batch) { // do work. batch.clear(); } public static class KeyedEvent { long key; Object data; } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/MultiProducerWithTranslator.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.EventTranslatorThreeArg; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.util.DaemonThreadFactory; public class MultiProducerWithTranslator { private static class IMessage { } private static class ITransportable { } private static class ObjectBox { IMessage message; ITransportable transportable; String string; private static final EventFactory FACTORY = ObjectBox::new; public void setMessage(final IMessage arg0) { message = arg0; } public void setTransportable(final ITransportable arg1) { transportable = arg1; } public void setStreamName(final String arg2) { string = arg2; } } public static class Publisher implements EventTranslatorThreeArg { @Override public void translateTo(final ObjectBox event, final long sequence, final IMessage arg0, final ITransportable arg1, final String arg2) { event.setMessage(arg0); event.setTransportable(arg1); event.setStreamName(arg2); } } public static class Consumer implements EventHandler { @Override public void onEvent(final ObjectBox event, final long sequence, final boolean endOfBatch) { } } static final int RING_SIZE = 1024; public static void main(final String[] args) throws InterruptedException { Disruptor disruptor = new Disruptor<>( ObjectBox.FACTORY, RING_SIZE, DaemonThreadFactory.INSTANCE, ProducerType.MULTI, new BlockingWaitStrategy()); disruptor.handleEventsWith(new Consumer()).then(new Consumer()); final RingBuffer ringBuffer = disruptor.getRingBuffer(); Publisher p = new Publisher(); IMessage message = new IMessage(); ITransportable transportable = new ITransportable(); String streamName = "com.lmax.wibble"; System.out.println("publishing " + RING_SIZE + " messages"); for (int i = 0; i < RING_SIZE; i++) { ringBuffer.publishEvent(p, message, transportable, streamName); Thread.sleep(10); } System.out.println("start disruptor"); disruptor.start(); System.out.println("continue publishing"); while (true) { ringBuffer.publishEvent(p, message, transportable, streamName); Thread.sleep(10); } } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/NamedEventHandler.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventHandler; public class NamedEventHandler implements EventHandler { private String oldName; private final String name; public NamedEventHandler(final String name) { this.name = name; } @Override public void onEvent(final T event, final long sequence, final boolean endOfBatch) { } @Override public void onStart() { final Thread currentThread = Thread.currentThread(); oldName = currentThread.getName(); currentThread.setName(name); } @Override public void onShutdown() { Thread.currentThread().setName(oldName); } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/Pipeliner.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; public class Pipeliner { public static void main(final String[] args) { Disruptor disruptor = new Disruptor<>( PipelinerEvent.FACTORY, 1024, DaemonThreadFactory.INSTANCE); disruptor.handleEventsWith( new ParallelHandler(0, 3), new ParallelHandler(1, 3), new ParallelHandler(2, 3) ).then(new JoiningHandler()); RingBuffer ringBuffer = disruptor.start(); for (int i = 0; i < 1000; i++) { long next = ringBuffer.next(); try { PipelinerEvent pipelinerEvent = ringBuffer.get(next); pipelinerEvent.input = i; } finally { ringBuffer.publish(next); } } } private static class ParallelHandler implements EventHandler { private final int ordinal; private final int totalHandlers; ParallelHandler(final int ordinal, final int totalHandlers) { this.ordinal = ordinal; this.totalHandlers = totalHandlers; } @Override public void onEvent(final PipelinerEvent event, final long sequence, final boolean endOfBatch) { if (sequence % totalHandlers == ordinal) { event.result = Long.toString(event.input); } } } private static class JoiningHandler implements EventHandler { private long lastEvent = -1; @Override public void onEvent(final PipelinerEvent event, final long sequence, final boolean endOfBatch) { if (event.input != lastEvent + 1 || event.result == null) { System.out.println("Error: " + event); } lastEvent = event.input; event.result = null; } } private static class PipelinerEvent { long input; Object result; private static final EventFactory FACTORY = PipelinerEvent::new; @Override public String toString() { return "PipelinerEvent{" + "input=" + input + ", result=" + result + '}'; } } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/PullWithBatchedPoller.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventPoller; import com.lmax.disruptor.RingBuffer; /** * Alternative usage of EventPoller, here we wrap it around BatchedEventPoller * to achieve Disruptor's batching. this speeds up the polling feature */ public class PullWithBatchedPoller { public static void main(final String[] args) throws Exception { int batchSize = 40; RingBuffer> ringBuffer = RingBuffer.createMultiProducer(BatchedPoller.DataEvent.factory(), 1024); BatchedPoller poller = new BatchedPoller<>(ringBuffer, batchSize); Object value = poller.poll(); // Value could be null if no events are available. if (null != value) { // Process value. } } static class BatchedPoller { private final EventPoller> poller; private final BatchedData polledData; BatchedPoller(final RingBuffer> ringBuffer, final int batchSize) { this.poller = ringBuffer.newPoller(); ringBuffer.addGatingSequences(poller.getSequence()); this.polledData = new BatchedData<>(batchSize); } public T poll() throws Exception { if (polledData.getMsgCount() > 0) { return polledData.pollMessage(); // we just fetch from our local } loadNextValues(poller, polledData); // we try to load from the ring return polledData.getMsgCount() > 0 ? polledData.pollMessage() : null; } private EventPoller.PollState loadNextValues(final EventPoller> poller, final BatchedData batch) throws Exception { return poller.poll((event, sequence, endOfBatch) -> { T item = event.copyOfData(); return item != null ? batch.addDataItem(item) : false; }); } public static class DataEvent { T data; public static EventFactory> factory() { return DataEvent::new; } public T copyOfData() { // Copy the data out here. In this case we have a single reference // object, so the pass by // reference is sufficient. But if we were reusing a byte array, // then we // would need to copy // the actual contents. return data; } void set(final T d) { data = d; } } private static class BatchedData { private int msgHighBound; private final int capacity; private final T[] data; private int cursor; @SuppressWarnings("unchecked") BatchedData(final int size) { this.capacity = size; data = (T[]) new Object[this.capacity]; } private void clearCount() { msgHighBound = 0; cursor = 0; } public int getMsgCount() { return msgHighBound - cursor; } public boolean addDataItem(final T item) throws IndexOutOfBoundsException { if (msgHighBound >= capacity) { throw new IndexOutOfBoundsException("Attempting to add item to full batch"); } data[msgHighBound++] = item; return msgHighBound < capacity; } public T pollMessage() { T rtVal = null; if (cursor < msgHighBound) { rtVal = data[cursor++]; } if (cursor > 0 && cursor >= msgHighBound) { clearCount(); } return rtVal; } } } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/PullWithPoller.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventPoller; import com.lmax.disruptor.RingBuffer; public class PullWithPoller { public static class DataEvent { T data; public static EventFactory> factory() { return DataEvent::new; } public T copyOfData() { // Copy the data out here. In this case we have a single reference object, so the pass by // reference is sufficient. But if we were reusing a byte array, then we would need to copy // the actual contents. return data; } } public static void main(final String[] args) throws Exception { RingBuffer> ringBuffer = RingBuffer.createMultiProducer(DataEvent.factory(), 1024); final EventPoller> poller = ringBuffer.newPoller(); Object value = getNextValue(poller); // Value could be null if no events are available. if (null != value) { // Process value. } } private static Object getNextValue(final EventPoller> poller) throws Exception { final Object[] out = new Object[1]; poller.poll( (event, sequence, endOfBatch) -> { out[0] = event.copyOfData(); // Return false so that only one event is processed at a time. return false; }); return out[0]; } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/SequentialThreeConsumers.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; public class SequentialThreeConsumers { private static class MyEvent { private Object a; private Object b; private Object c; private Object d; } public static void main(final String[] args) { Disruptor disruptor = new Disruptor<>(MyEvent::new, 1024, DaemonThreadFactory.INSTANCE); disruptor.handleEventsWith((event, sequence, endOfBatch) -> event.b = event.a) .then((event, sequence, endOfBatch) -> event.c = event.b) .then((event, sequence, endOfBatch) -> event.d = event.c); disruptor.start(); } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/ShutdownOnError.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.ExceptionHandler; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; public class ShutdownOnError { private static class Event { public long value; public static final EventFactory FACTORY = Event::new; } private static class Handler implements EventHandler { @Override public void onEvent(final Event event, final long sequence, final boolean endOfBatch) { // do work, if a failure occurs throw exception. } } private static final class ErrorHandler implements ExceptionHandler { private final AtomicBoolean running; private ErrorHandler(final AtomicBoolean running) { this.running = running; } @Override public void handleEventException(final Throwable ex, final long sequence, final Event event) { if (execeptionIsFatal(ex)) { throw new RuntimeException(ex); } } private boolean execeptionIsFatal(final Throwable ex) { // Do what is appropriate here. return true; } @Override public void handleOnStartException(final Throwable ex) { } @Override public void handleOnShutdownException(final Throwable ex) { } } public static void main(final String[] args) { Disruptor disruptor = new Disruptor<>(Event.FACTORY, 1024, DaemonThreadFactory.INSTANCE); AtomicBoolean running = new AtomicBoolean(true); ErrorHandler errorHandler = new ErrorHandler(running); final Handler handler = new Handler(); disruptor.handleEventsWith(handler); disruptor.handleExceptionsFor(handler).with(errorHandler); simplePublish(disruptor, running); } private static void simplePublish(final Disruptor disruptor, final AtomicBoolean running) { while (running.get()) { disruptor.publishEvent((event, sequence) -> event.value = sequence); } } private static void smarterPublish(final Disruptor disruptor, final AtomicBoolean running) { final RingBuffer ringBuffer = disruptor.getRingBuffer(); boolean publishOk; do { publishOk = ringBuffer.tryPublishEvent((event, sequence) -> event.value = sequence); } while (publishOk && running.get()); } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/ThreeToOneDisruptor.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; public class ThreeToOneDisruptor { public static class DataEvent { Object input; Object[] output; public DataEvent(final int size) { output = new Object[size]; } public static final EventFactory FACTORY = () -> new DataEvent(3); } public static class TransformingHandler implements EventHandler { private final int outputIndex; public TransformingHandler(final int outputIndex) { this.outputIndex = outputIndex; } @Override public void onEvent(final DataEvent event, final long sequence, final boolean endOfBatch) { // Do Stuff. event.output[outputIndex] = doSomething(event.input); } private Object doSomething(final Object input) { // Do required transformation here.... return input; } } public static class CollatingHandler implements EventHandler { @Override public void onEvent(final DataEvent event, final long sequence, final boolean endOfBatch) { collate(event.output); } private void collate(final Object[] output) { // Do required collation here.... } } public static void main(final String[] args) { Disruptor disruptor = new Disruptor<>( DataEvent.FACTORY, 1024, DaemonThreadFactory.INSTANCE); TransformingHandler handler1 = new TransformingHandler(0); TransformingHandler handler2 = new TransformingHandler(1); TransformingHandler handler3 = new TransformingHandler(2); CollatingHandler collator = new CollatingHandler(); disruptor.handleEventsWith(handler1, handler2, handler3).then(collator); disruptor.start(); } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/WaitForProcessing.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.EventTranslator; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.examples.support.LongEvent; import com.lmax.disruptor.util.DaemonThreadFactory; public class WaitForProcessing { public static class Consumer implements EventHandler { @Override public void onEvent(final LongEvent event, final long sequence, final boolean endOfBatch) { } } public static void main(final String[] args) { final Disruptor disruptor = new Disruptor<>( LongEvent.FACTORY, 1024, DaemonThreadFactory.INSTANCE); Consumer firstConsumer = new Consumer(); Consumer lastConsumer = new Consumer(); disruptor.handleEventsWith(firstConsumer).then(lastConsumer); final RingBuffer ringBuffer = disruptor.getRingBuffer(); EventTranslator translator = (event, sequence) -> event.set(sequence - 4); ringBuffer.tryPublishEvent(translator); waitForSpecificConsumer(disruptor, lastConsumer, ringBuffer); waitForRingBufferToBeIdle(ringBuffer); } @SuppressWarnings("StatementWithEmptyBody") private static void waitForRingBufferToBeIdle(final RingBuffer ringBuffer) { while (ringBuffer.getBufferSize() - ringBuffer.remainingCapacity() != 0) { // Wait for priocessing... } } private static void waitForSpecificConsumer( final Disruptor disruptor, final Consumer lastConsumer, final RingBuffer ringBuffer) { long lastPublishedValue; long sequenceValueFor; do { lastPublishedValue = ringBuffer.getCursor(); sequenceValueFor = disruptor.getSequenceValueFor(lastConsumer); } while (sequenceValueFor < lastPublishedValue); } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/WaitForShutdown.java ================================================ package com.lmax.disruptor.examples; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.TimeoutException; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.examples.support.LongEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class WaitForShutdown { private static volatile int value = 0; private static class Handler implements EventHandler { private final CountDownLatch latch; Handler(final CountDownLatch latch) { this.latch = latch; } @Override public void onStart() { } @Override public void onShutdown() { latch.countDown(); } @Override public void onEvent(final LongEvent event, final long sequence, final boolean endOfBatch) { value = 1; } } public static void main(final String[] args) throws TimeoutException, InterruptedException { Disruptor disruptor = new Disruptor<>( LongEvent.FACTORY, 16, DaemonThreadFactory.INSTANCE ); CountDownLatch shutdownLatch = new CountDownLatch(2); disruptor.handleEventsWith(new Handler(shutdownLatch)).then(new Handler(shutdownLatch)); disruptor.start(); long next = disruptor.getRingBuffer().next(); disruptor.getRingBuffer().get(next).set(next); disruptor.getRingBuffer().publish(next); disruptor.shutdown(10, TimeUnit.SECONDS); shutdownLatch.await(); System.out.println(value); } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/LongEvent.java ================================================ package com.lmax.disruptor.examples.longevent; // tag::example[] public class LongEvent { private long value; public void set(long value) { this.value = value; } @Override public String toString() { return "LongEvent{" + "value=" + value + '}'; } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/LongEventFactory.java ================================================ package com.lmax.disruptor.examples.longevent; import com.lmax.disruptor.EventFactory; // tag::example[] public class LongEventFactory implements EventFactory { @Override public LongEvent newInstance() { return new LongEvent(); } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/LongEventHandler.java ================================================ package com.lmax.disruptor.examples.longevent; import com.lmax.disruptor.EventHandler; // tag::example[] public class LongEventHandler implements EventHandler { @Override public void onEvent(LongEvent event, long sequence, boolean endOfBatch) { System.out.println("Event: " + event); } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/LongEventProducer.java ================================================ package com.lmax.disruptor.examples.longevent; // tag::example[] import com.lmax.disruptor.EventTranslatorOneArg; import com.lmax.disruptor.RingBuffer; import java.nio.ByteBuffer; public class LongEventProducer { private final RingBuffer ringBuffer; public LongEventProducer(RingBuffer ringBuffer) { this.ringBuffer = ringBuffer; } private static final EventTranslatorOneArg TRANSLATOR = new EventTranslatorOneArg() { @Override public void translateTo(LongEvent event, long sequence, ByteBuffer bb) { event.set(bb.getLong(0)); } }; public void onData(ByteBuffer bb) { ringBuffer.publishEvent(TRANSLATOR, bb); } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/LongEventProducerWithTranslator.java ================================================ package com.lmax.disruptor.examples.longevent; // tag::example[] import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.EventTranslatorOneArg; import java.nio.ByteBuffer; public class LongEventProducerWithTranslator { private final RingBuffer ringBuffer; public LongEventProducerWithTranslator(RingBuffer ringBuffer) { this.ringBuffer = ringBuffer; } private static final EventTranslatorOneArg TRANSLATOR = new EventTranslatorOneArg() { @Override public void translateTo(LongEvent event, long sequence, ByteBuffer bb) { event.set(bb.getLong(0)); } }; public void onData(ByteBuffer bb) { ringBuffer.publishEvent(TRANSLATOR, bb); } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/lambdas/LongEventMain.java ================================================ package com.lmax.disruptor.examples.longevent.lambdas; // tag::example[] import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.examples.longevent.LongEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import java.nio.ByteBuffer; public class LongEventMain { public static void main(String[] args) throws Exception { int bufferSize = 1024; // <1> Disruptor disruptor = // <2> new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE); disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event)); // <3> disruptor.start(); // <4> RingBuffer ringBuffer = disruptor.getRingBuffer(); // <5> ByteBuffer bb = ByteBuffer.allocate(8); for (long l = 0; true; l++) { bb.putLong(0, l); ringBuffer.publishEvent((event, sequence, buffer) -> event.set(buffer.getLong(0)), bb); Thread.sleep(1000); } } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/legacy/LongEventMain.java ================================================ package com.lmax.disruptor.examples.longevent.legacy; // tag::example[] import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.examples.longevent.LongEvent; import com.lmax.disruptor.examples.longevent.LongEventFactory; import com.lmax.disruptor.examples.longevent.LongEventHandler; import com.lmax.disruptor.util.DaemonThreadFactory; import java.nio.ByteBuffer; public class LongEventMain { public static void main(String[] args) throws Exception { LongEventFactory factory = new LongEventFactory(); int bufferSize = 1024; Disruptor disruptor = new Disruptor<>(factory, bufferSize, DaemonThreadFactory.INSTANCE); disruptor.handleEventsWith(new LongEventHandler()); disruptor.start(); RingBuffer ringBuffer = disruptor.getRingBuffer(); LongEventProducer producer = new LongEventProducer(ringBuffer); ByteBuffer bb = ByteBuffer.allocate(8); for (long l = 0; true; l++) { bb.putLong(0, l); producer.onData(bb); Thread.sleep(1000); } } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/legacy/LongEventProducer.java ================================================ package com.lmax.disruptor.examples.longevent.legacy; // tag::example[] import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.examples.longevent.LongEvent; import java.nio.ByteBuffer; public class LongEventProducer { private final RingBuffer ringBuffer; public LongEventProducer(RingBuffer ringBuffer) { this.ringBuffer = ringBuffer; } public void onData(ByteBuffer bb) { long sequence = ringBuffer.next(); // <1> try { LongEvent event = ringBuffer.get(sequence); // <2> event.set(bb.getLong(0)); // <3> } finally { ringBuffer.publish(sequence); } } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/longevent/methodrefs/LongEventMain.java ================================================ package com.lmax.disruptor.examples.longevent.methodrefs; // tag::example[] import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.examples.longevent.LongEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import java.nio.ByteBuffer; public class LongEventMain { public static void handleEvent(LongEvent event, long sequence, boolean endOfBatch) { System.out.println(event); } public static void translate(LongEvent event, long sequence, ByteBuffer buffer) { event.set(buffer.getLong(0)); } public static void main(String[] args) throws Exception { int bufferSize = 1024; Disruptor disruptor = new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE); disruptor.handleEventsWith(LongEventMain::handleEvent); disruptor.start(); RingBuffer ringBuffer = disruptor.getRingBuffer(); ByteBuffer bb = ByteBuffer.allocate(8); for (long l = 0; true; l++) { bb.putLong(0, l); ringBuffer.publishEvent(LongEventMain::translate, bb); Thread.sleep(1000); } } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/objectevent/ClearingEventHandler.java ================================================ package com.lmax.disruptor.examples.objectevent; // tag::example[] import com.lmax.disruptor.EventHandler; public class ClearingEventHandler implements EventHandler> { @Override public void onEvent(ObjectEvent event, long sequence, boolean endOfBatch) { event.clear(); // <1> } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/objectevent/Main.java ================================================ package com.lmax.disruptor.examples.objectevent; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; @SuppressWarnings("unchecked") public class Main { private static final int BUFFER_SIZE = 1024; // tag::example[] public static void main(String[] args) { Disruptor> disruptor = new Disruptor<>( () -> new ObjectEvent<>(), BUFFER_SIZE, DaemonThreadFactory.INSTANCE); disruptor .handleEventsWith(new ProcessingEventHandler()) .then(new ClearingEventHandler()); } // end::example[] } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/objectevent/ObjectEvent.java ================================================ package com.lmax.disruptor.examples.objectevent; // tag::example[] class ObjectEvent { T val; void clear() { val = null; } } // end::example[] ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/objectevent/ProcessingEventHandler.java ================================================ package com.lmax.disruptor.examples.objectevent; import com.lmax.disruptor.EventHandler; public class ProcessingEventHandler implements EventHandler> { @Override public void onEvent(ObjectEvent event, long sequence, boolean endOfBatch) throws Exception { } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/support/LongEvent.java ================================================ package com.lmax.disruptor.examples.support; import com.lmax.disruptor.EventFactory; public class LongEvent { public static final EventFactory FACTORY = LongEvent::new; private long value; public void set(final long value) { this.value = value; } public long get() { return value; } } ================================================ FILE: src/examples/java/com/lmax/disruptor/examples/support/StubEvent.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.examples.support; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventTranslatorTwoArg; public final class StubEvent { private int value; private String testString; public static final EventTranslatorTwoArg TRANSLATOR = (event, sequence, arg0, arg1) -> { event.setValue(arg0); event.setTestString(arg1); }; public StubEvent(final int i) { this.value = i; } public void copy(final StubEvent event) { value = event.value; } public int getValue() { return value; } public void setValue(final int value) { this.value = value; } public String getTestString() { return testString; } public void setTestString(final String testString) { this.testString = testString; } public static final EventFactory EVENT_FACTORY = () -> new StubEvent(-1); @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final StubEvent stubEvent = (StubEvent) o; if (value != stubEvent.value) { return false; } return testString != null ? testString.equals(stubEvent.testString) : stubEvent.testString == null; } @Override public int hashCode() { int result = value; result = 31 * result + (testString != null ? testString.hashCode() : 0); return result; } } ================================================ FILE: src/jcstress/java/com/lmax/disruptor/LoggerInitializationStress.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.util.UnsafeAccess; import org.openjdk.jcstress.annotations.Actor; import org.openjdk.jcstress.annotations.JCStressTest; import org.openjdk.jcstress.annotations.Mode; import org.openjdk.jcstress.annotations.Outcome; import org.openjdk.jcstress.annotations.Signal; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.concurrent.Executors; import java.util.logging.LogManager; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE; import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN; /** * Validate that creating a {@link Disruptor} instance with a custom {@link ExceptionHandler} does not * initialize the logging framework. Using JCStress is a stretch, we're not validating a race condition, * rather that the logging framework is not started. This type of test doesn't work well in unit tests * because it requires JVM isolation, and any interactions with a logger invalidate the test. */ @JCStressTest(Mode.Termination) @Outcome(id = "TERMINATED", expect = ACCEPTABLE, desc = "Logger has not been initialized") @Outcome( id = {"STALE", "ERROR"}, expect = FORBIDDEN, desc = "Logger has been initialized. This has the potential to cause " + "deadlocks when disruptor is used within logging frameworks") public class LoggerInitializationStress { private static volatile boolean logManagerInitialized; private static volatile boolean disruptorInitialized; static { System.setProperty("java.util.logging.manager", DisruptorLogManager.class.getCanonicalName()); } @Actor public void actor() throws Exception { // Wait for a the Disruptor instance to be initialized while (!disruptorInitialized) { } // Validate state if (logManagerInitialized) { throw new RuntimeException("Expected the LogManager to be uninitialized"); } // Use Unsafe to determine if the LogManager has been initialized. // Accessing the LogManager normally using LogManager.getLogManager // results in initialization which modifies static state and prevents // subsequent runs from executing successfully. Field managerField = LogManager.class.getDeclaredField("manager"); Unsafe unsafe = UnsafeAccess.getUnsafe(); Object managerBase = unsafe.staticFieldBase(managerField); long managerOffset = unsafe.staticFieldOffset(managerField); Object logManager = unsafe.getObject(managerBase, managerOffset); if (logManager != null) { throw new RuntimeException("Unexpected LogManager: " + logManager); } } @Signal public void signal() { final Disruptor disruptor = new Disruptor<>( SimpleEvent::new, 128, Executors.defaultThreadFactory(), ProducerType.MULTI, new BlockingWaitStrategy()); disruptor.setDefaultExceptionHandler(SimpleEventExceptionHandler.INSTANCE); disruptor.handleEventsWith(SimpleEventHandler.INSTANCE); disruptor.start(); disruptor.halt(); disruptorInitialized = true; } public static final class DisruptorLogManager extends LogManager { static { logManagerInitialized = true; } } public static final class SimpleEvent { private long value = Long.MIN_VALUE; public long getValue() { return value; } public void setValue(final long value) { this.value = value; } @Override public String toString() { return "SimpleEvent{" + "value=" + value + '}'; } } public enum SimpleEventHandler implements EventHandler { INSTANCE; @Override public void onEvent(final SimpleEvent event, final long sequence, final boolean endOfBatch) { // nop } } public enum SimpleEventExceptionHandler implements ExceptionHandler { INSTANCE; @Override public void handleEventException(final Throwable ex, final long sequence, final SimpleEvent event) { // nop } @Override public void handleOnStartException(final Throwable ex) { // nop } @Override public void handleOnShutdownException(final Throwable ex) { // nop } } } ================================================ FILE: src/jcstress/java/com/lmax/disruptor/MultiProducerSequencerUnsafeStress.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.alternatives.MultiProducerSequencerUnsafe; import org.openjdk.jcstress.annotations.Actor; import org.openjdk.jcstress.annotations.JCStressTest; import org.openjdk.jcstress.annotations.Outcome; import org.openjdk.jcstress.annotations.State; import org.openjdk.jcstress.infra.results.ZZ_Result; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE; import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN; public final class MultiProducerSequencerUnsafeStress { private static Sequencer createSequencer() { return new MultiProducerSequencerUnsafe(64, new BlockingWaitStrategy()); } @JCStressTest @Outcome(id = {"false, false", "true, false", "true, true"}, expect = ACCEPTABLE, desc = "Assuming ordered updates") @Outcome(id = "false, true", expect = FORBIDDEN, desc = "publish(2) should not be available before publish(1)") @State public static class PublishUpdatesIsAvailableLazily { Sequencer sequencer = createSequencer(); @Actor public void actor1() { // The store to the underlying availableBuffer is lazily done and stores can be visible out of order sequencer.publish(1); sequencer.publish(2); } @Actor public void actor2(final ZZ_Result r) { // The load in isAvailable is a full store/load barrier, so any stores from actor1 should get flushed r.r2 = sequencer.isAvailable(2); r.r1 = sequencer.isAvailable(1); } } /** * The isAvailable implementation is volatile so we should never see an update to it without seeing the update to a * previously set value also. * *

If the value was not volatile there would be no ordering rules stopping it being seen updated before the * other value. */ @JCStressTest @Outcome(id = "false, false", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "true, true", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "false, true", expect = ACCEPTABLE, desc = "Caught in the middle: $x is visible, $y is not.") @Outcome(id = "true, false", expect = FORBIDDEN, desc = "Seeing $y, but not $x!") @State public static class GetVolatile { boolean x = false; Sequencer y = createSequencer(); @Actor public void actor1() { x = true; y.publish(1); } @Actor public void actor2(final ZZ_Result r) { r.r1 = y.isAvailable(1); r.r2 = x; } } /** * In absence of synchronization, the order of independent reads is undefined. * In our case, the read of isAvailable is volatile which mandates the writes to the same * variable to be observed in a total order (that implies that _observers_ are also ordered) */ @JCStressTest @Outcome(id = "false, false", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "true, true", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "false, true", expect = ACCEPTABLE, desc = "Doing first read early, not surprising.") @Outcome(id = "true, false", expect = FORBIDDEN, desc = "Violates coherence.") @State public static class SameVolatileRead { private final SameVolatileRead.Holder h1 = new SameVolatileRead.Holder(); private final SameVolatileRead.Holder h2 = h1; private static class Holder { Sequencer sequence = createSequencer(); } @Actor public void actor1() { h1.sequence.publish(1); } @Actor public void actor2(final ZZ_Result r) { SameVolatileRead.Holder h1 = this.h1; SameVolatileRead.Holder h2 = this.h2; r.r1 = h1.sequence.isAvailable(1); r.r2 = h2.sequence.isAvailable(1); } } } ================================================ FILE: src/jcstress/java/com/lmax/disruptor/MultiProducerSequencerVarHandleStress.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.alternatives.MultiProducerSequencerVarHandle; import org.openjdk.jcstress.annotations.Actor; import org.openjdk.jcstress.annotations.JCStressTest; import org.openjdk.jcstress.annotations.Outcome; import org.openjdk.jcstress.annotations.State; import org.openjdk.jcstress.infra.results.ZZ_Result; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE; import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN; public final class MultiProducerSequencerVarHandleStress { private static Sequencer createSequencer() { return new MultiProducerSequencerVarHandle(64, new BlockingWaitStrategy()); } @JCStressTest @Outcome(id = {"false, false", "true, false", "true, true"}, expect = ACCEPTABLE, desc = "Assuming ordered updates") @Outcome(id = "false, true", expect = FORBIDDEN, desc = "publish(2) should not be available before publish(1)") @State public static class PublishUpdatesIsAvailableLazily { Sequencer sequencer = createSequencer(); @Actor public void actor1() { sequencer.publish(1); sequencer.publish(2); } @Actor public void actor2(final ZZ_Result r) { r.r2 = sequencer.isAvailable(2); r.r1 = sequencer.isAvailable(1); } } /** * The isAvailable implementation is volatile so we should never see an update to it without seeing the update to a * previously set value also. * *

If the value was not volatile there would be no ordering rules stopping it being seen updated before the * other value. */ @JCStressTest @Outcome(id = "false, false", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "true, true", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "false, true", expect = ACCEPTABLE, desc = "Caught in the middle: $x is visible, $y is not.") @Outcome(id = "true, false", expect = FORBIDDEN, desc = "Seeing $y, but not $x!") @State public static class GetVolatile { boolean x = false; Sequencer y = createSequencer(); @Actor public void actor1() { x = true; y.publish(1); } @Actor public void actor2(final ZZ_Result r) { r.r1 = y.isAvailable(1); r.r2 = x; } } /** * In absence of synchronization, the order of independent reads is undefined. * In our case, the read of isAvailable is volatile which mandates the writes to the same * variable to be observed in a total order (that implies that _observers_ are also ordered) */ @JCStressTest @Outcome(id = "false, false", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "true, true", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "false, true", expect = ACCEPTABLE, desc = "Doing first read early, not surprising.") @Outcome(id = "true, false", expect = FORBIDDEN, desc = "Violates coherence.") @State public static class SameVolatileRead { private final Holder h1 = new Holder(); private final Holder h2 = h1; private static class Holder { Sequencer sequence = createSequencer(); } @Actor public void actor1() { h1.sequence.publish(1); } @Actor public void actor2(final ZZ_Result r) { Holder h1 = this.h1; Holder h2 = this.h2; r.r1 = h1.sequence.isAvailable(1); r.r2 = h2.sequence.isAvailable(1); } } } ================================================ FILE: src/jcstress/java/com/lmax/disruptor/SequenceStressUnsafe.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.alternatives.SequenceUnsafe; import org.openjdk.jcstress.annotations.Actor; import org.openjdk.jcstress.annotations.Arbiter; import org.openjdk.jcstress.annotations.JCStressTest; import org.openjdk.jcstress.annotations.Outcome; import org.openjdk.jcstress.annotations.Ref; import org.openjdk.jcstress.annotations.State; import org.openjdk.jcstress.infra.results.JJ_Result; import org.openjdk.jcstress.infra.results.J_Result; import org.openjdk.jcstress.infra.results.ZZJ_Result; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE_INTERESTING; import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN; public class SequenceStressUnsafe { /** * `SequenceUnsafe::incrementAndGet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = "1", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "2", expect = ACCEPTABLE, desc = "Both updates.") @State public static class IncrementAndGet { SequenceUnsafe sequence = new SequenceUnsafe(0); @Actor public void actor1() { sequence.incrementAndGet(); } @Actor public void actor2() { sequence.incrementAndGet(); } @Arbiter public void arbiter(final J_Result r) { r.r1 = sequence.get(); } } /** * `SequenceUnsafe::compareAndSet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = {"true, false, 10", "false, true, 20"}, expect = ACCEPTABLE, desc = "Either updated.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @State public static class CompareAndSet { SequenceUnsafe sequence = new SequenceUnsafe(0); @Actor public void actor1(final ZZJ_Result r) { r.r1 = sequence.compareAndSet(0, 10); } @Actor public void actor2(final ZZJ_Result r) { r.r2 = sequence.compareAndSet(0, 20); } @Arbiter public void arbiter(final ZZJ_Result r) { r.r3 = sequence.get(); } } /** * `SequenceUnsafe::addAndGet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = "10", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "20", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "30", expect = ACCEPTABLE, desc = "Both updates.") @State public static class AddAndGet { SequenceUnsafe sequence = new SequenceUnsafe(0); @Actor public void actor1() { sequence.addAndGet(10); } @Actor public void actor2() { sequence.addAndGet(20); } @Arbiter public void arbiter(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceUnsafe should store its underlying value as a volatile long and therefore should not experience this effect * even when a non-volatile UNSAFE set method is used. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullSet { SequenceUnsafe sequence = new SequenceUnsafe(0); @Actor public void writer() { sequence.set(0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceUnsafe should store its underlying value as a volatile long and therefore should not experience this effect. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullSetVolatile { SequenceUnsafe sequence = new SequenceUnsafe(0); @Actor public void writer() { sequence.setVolatile(0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceUnsafe should store its underlying value as a volatile long and therefore should not experience this effect. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullCompareAndSet { SequenceUnsafe sequence = new SequenceUnsafe(0); @Actor public void writer() { sequence.compareAndSet(0, 0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * In absence of synchronization, the order of independent reads is undefined. * In our case, the value in SequenceUnsafe is volatile which mandates the writes to the same * variable to be observed in a total order (that implies that _observers_ are also ordered) */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Doing first read early, not surprising.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Violates coherence.") @State public static class SameVolatileRead { private final Holder h1 = new Holder(); private final Holder h2 = h1; private static class Holder { SequenceUnsafe sequence = new SequenceUnsafe(0); } @Actor public void actor1() { h1.sequence.set(1); } @Actor public void actor2(final JJ_Result r) { Holder h1 = this.h1; Holder h2 = this.h2; r.r1 = h1.sequence.get(); r.r2 = h2.sequence.get(); } } /** * The value field in SequenceUnsafe is volatile so we should never see an update to it without seeing the update to a * previously set value also. * *

If the value was not volatile there would be no ordering rules stopping it being seen updated before the * other value. */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Caught in the middle: $x is visible, $y is not.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Seeing $y, but not $x!") @State public static class SetVolatileGuard { long x = 0; SequenceUnsafe y = new SequenceUnsafe(0); @Actor public void actor1() { x = 1; y.setVolatile(1); } @Actor public void actor2(final JJ_Result r) { r.r1 = y.get(); r.r2 = x; } } /** * The value field in SequenceUnsafe is volatile so we should never see an update to it without seeing the update to a * previously set value also. * *

If the value was not volatile there would be no ordering rules stopping it being seen updated before the * other value. * *

This is a property of the field, not a property of the method used to set the value of it. */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Caught in the middle: $x is visible, $y is not.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Seeing $y, but not $x!") @State public static class SetGuard { long x = 0; SequenceUnsafe y = new SequenceUnsafe(0); @Actor public void actor1() { x = 1; y.set(1); } @Actor public void actor2(final JJ_Result r) { r.r1 = y.get(); r.r2 = x; } } /** * Volatile setting will experience total ordering. */ @JCStressTest @Outcome(id = {"0, 1", "1, 0", "1, 1"}, expect = ACCEPTABLE, desc = "Trivial under sequential consistency") @Outcome(id = "0, 0", expect = FORBIDDEN, desc = "Violates sequential consistency") @State public static class SetVolatileDekker { SequenceUnsafe x = new SequenceUnsafe(0); SequenceUnsafe y = new SequenceUnsafe(0); @Actor public void actor1(final JJ_Result r) { x.setVolatile(1); r.r1 = y.get(); } @Actor public void actor2(final JJ_Result r) { y.setVolatile(1); r.r2 = x.get(); } } /** * Non-volatile setting will not experience total ordering, those gets can be re-ordered and happen before either set. */ @JCStressTest @Outcome(id = {"0, 1", "1, 0", "1, 1"}, expect = ACCEPTABLE, desc = "Trivial under sequential consistency") @Outcome(id = "0, 0", expect = ACCEPTABLE_INTERESTING, desc = "Violates sequential consistency") @State public static class SetDekker { SequenceUnsafe x = new SequenceUnsafe(0); SequenceUnsafe y = new SequenceUnsafe(0); @Actor public void actor1(final JJ_Result r) { x.set(1); r.r1 = y.get(); } @Actor public void actor2(final JJ_Result r) { y.set(1); r.r2 = x.get(); } } } ================================================ FILE: src/jcstress/java/com/lmax/disruptor/SequenceStressVarHandle.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.alternatives.SequenceVarHandle; import org.openjdk.jcstress.annotations.Actor; import org.openjdk.jcstress.annotations.Arbiter; import org.openjdk.jcstress.annotations.JCStressTest; import org.openjdk.jcstress.annotations.Outcome; import org.openjdk.jcstress.annotations.Ref; import org.openjdk.jcstress.annotations.State; import org.openjdk.jcstress.infra.results.JJ_Result; import org.openjdk.jcstress.infra.results.J_Result; import org.openjdk.jcstress.infra.results.ZZJ_Result; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE_INTERESTING; import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN; public class SequenceStressVarHandle { /** * `SequenceVarHandle::incrementAndGet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = "1", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "2", expect = ACCEPTABLE, desc = "Both updates.") @State public static class IncrementAndGet { SequenceVarHandle sequence = new SequenceVarHandle(0); @Actor public void actor1() { sequence.incrementAndGet(); } @Actor public void actor2() { sequence.incrementAndGet(); } @Arbiter public void arbiter(final J_Result r) { r.r1 = sequence.get(); } } /** * `SequenceVarHandle::compareAndSet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = {"true, false, 10", "false, true, 20"}, expect = ACCEPTABLE, desc = "Either updated.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @State public static class CompareAndSet { SequenceVarHandle sequence = new SequenceVarHandle(0); @Actor public void actor1(final ZZJ_Result r) { r.r1 = sequence.compareAndSet(0, 10); } @Actor public void actor2(final ZZJ_Result r) { r.r2 = sequence.compareAndSet(0, 20); } @Arbiter public void arbiter(final ZZJ_Result r) { r.r3 = sequence.get(); } } /** * `SequenceVarHandle::addAndGet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = "10", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "20", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "30", expect = ACCEPTABLE, desc = "Both updates.") @State public static class AddAndGet { SequenceVarHandle sequence = new SequenceVarHandle(0); @Actor public void actor1() { sequence.addAndGet(10); } @Actor public void actor2() { sequence.addAndGet(20); } @Arbiter public void arbiter(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceVarHandle should store its underlying value as a volatile long and therefore should not experience this effect * even when a non-volatile UNSAFE set method is used. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullSet { SequenceVarHandle sequence = new SequenceVarHandle(0); @Actor public void writer() { sequence.set(0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceVarHandle should store its underlying value as a volatile long and therefore should not experience this effect. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullSetVolatile { SequenceVarHandle sequence = new SequenceVarHandle(0); @Actor public void writer() { sequence.setVolatile(0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceVarHandle should store its underlying value as a volatile long and therefore should not experience this effect. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullCompareAndSet { SequenceVarHandle sequence = new SequenceVarHandle(0); @Actor public void writer() { sequence.compareAndSet(0, 0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * In absence of synchronization, the order of independent reads is undefined. * In our case, the value in SequenceVarHandle is volatile which mandates the writes to the same * variable to be observed in a total order (that implies that _observers_ are also ordered) */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Doing first read early, not surprising.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Violates coherence.") @State public static class SameVolatileRead { private final Holder h1 = new Holder(); private final Holder h2 = h1; private static class Holder { SequenceVarHandle sequence = new SequenceVarHandle(0); } @Actor public void actor1() { h1.sequence.set(1); } @Actor public void actor2(final JJ_Result r) { Holder h1 = this.h1; Holder h2 = this.h2; r.r1 = h1.sequence.get(); r.r2 = h2.sequence.get(); } } /** * The value field in SequenceVarHandle is volatile so we should never see an update to it without seeing the update to a * previously set value also. * *

If the value was not volatile there would be no ordering rules stopping it being seen updated before the * other value. */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Caught in the middle: $x is visible, $y is not.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Seeing $y, but not $x!") @State public static class SetVolatileGuard { long x = 0; SequenceVarHandle y = new SequenceVarHandle(0); @Actor public void actor1() { x = 1; y.setVolatile(1); } @Actor public void actor2(final JJ_Result r) { r.r1 = y.get(); r.r2 = x; } } /** * The value field in SequenceVarHandle is volatile so we should never see an update to it without seeing the update to a * previously set value also. * *

If the value was not volatile there would be no ordering rules stopping it being seen updated before the * other value. * *

This is a property of the field, not a property of the method used to set the value of it. */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Caught in the middle: $x is visible, $y is not.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Seeing $y, but not $x!") @State public static class SetGuard { long x = 0; SequenceVarHandle y = new SequenceVarHandle(0); @Actor public void actor1() { x = 1; y.set(1); } @Actor public void actor2(final JJ_Result r) { r.r1 = y.get(); r.r2 = x; } } /** * Volatile setting will experience total ordering. */ @JCStressTest @Outcome(id = {"0, 1", "1, 0", "1, 1"}, expect = ACCEPTABLE, desc = "Trivial under sequential consistency") @Outcome(id = "0, 0", expect = FORBIDDEN, desc = "Violates sequential consistency") @State public static class SetVolatileDekker { SequenceVarHandle x = new SequenceVarHandle(0); SequenceVarHandle y = new SequenceVarHandle(0); @Actor public void actor1(final JJ_Result r) { x.setVolatile(1); r.r1 = y.get(); } @Actor public void actor2(final JJ_Result r) { y.setVolatile(1); r.r2 = x.get(); } } /** * Non-volatile setting will not experience total ordering, those gets can be re-ordered and happen before either set. */ @JCStressTest @Outcome(id = {"0, 1", "1, 0", "1, 1"}, expect = ACCEPTABLE, desc = "Trivial under sequential consistency") @Outcome(id = "0, 0", expect = ACCEPTABLE_INTERESTING, desc = "Violates sequential consistency") @State public static class SetDekker { SequenceVarHandle x = new SequenceVarHandle(0); SequenceVarHandle y = new SequenceVarHandle(0); @Actor public void actor1(final JJ_Result r) { x.set(1); r.r1 = y.get(); } @Actor public void actor2(final JJ_Result r) { y.set(1); r.r2 = x.get(); } } } ================================================ FILE: src/jcstress/java/com/lmax/disruptor/SequenceStressVarHandleBarrier.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.alternatives.SequenceVarHandleBarrier; import org.openjdk.jcstress.annotations.Actor; import org.openjdk.jcstress.annotations.Arbiter; import org.openjdk.jcstress.annotations.JCStressTest; import org.openjdk.jcstress.annotations.Outcome; import org.openjdk.jcstress.annotations.Ref; import org.openjdk.jcstress.annotations.State; import org.openjdk.jcstress.infra.results.JJ_Result; import org.openjdk.jcstress.infra.results.J_Result; import org.openjdk.jcstress.infra.results.ZZJ_Result; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE; import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE_INTERESTING; import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN; public class SequenceStressVarHandleBarrier { /** * `com.lmax.disruptor.alternatives.SequenceVarHandleBarrier::incrementAndGet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = "1", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "2", expect = ACCEPTABLE, desc = "Both updates.") @State public static class IncrementAndGet { SequenceVarHandleBarrier sequence = new SequenceVarHandleBarrier(0); @Actor public void actor1() { sequence.incrementAndGet(); } @Actor public void actor2() { sequence.incrementAndGet(); } @Arbiter public void arbiter(final J_Result r) { r.r1 = sequence.get(); } } /** * `SequenceVarHandleBarrier::compareAndSet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = {"true, false, 10", "false, true, 20"}, expect = ACCEPTABLE, desc = "Either updated.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @State public static class CompareAndSet { SequenceVarHandleBarrier sequence = new SequenceVarHandleBarrier(0); @Actor public void actor1(final ZZJ_Result r) { r.r1 = sequence.compareAndSet(0, 10); } @Actor public void actor2(final ZZJ_Result r) { r.r2 = sequence.compareAndSet(0, 20); } @Arbiter public void arbiter(final ZZJ_Result r) { r.r3 = sequence.get(); } } /** * `SequenceVarHandleBarrier::addAndGet` is atomic and should never lose an update, even with multiple threads racing. */ @JCStressTest @Outcome(id = "10", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "20", expect = FORBIDDEN, desc = "One update lost.") @Outcome(id = "30", expect = ACCEPTABLE, desc = "Both updates.") @State public static class AddAndGet { SequenceVarHandleBarrier sequence = new SequenceVarHandleBarrier(0); @Actor public void actor1() { sequence.addAndGet(10); } @Actor public void actor2() { sequence.addAndGet(20); } @Arbiter public void arbiter(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceVarHandleBarrier should store its underlying value as a volatile long and therefore should not experience this effect * even when a non-volatile UNSAFE set method is used. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullSet { SequenceVarHandleBarrier sequence = new SequenceVarHandleBarrier(0); @Actor public void writer() { sequence.set(0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceVarHandleBarrier should store its underlying value as a volatile long and therefore should not experience this effect. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullSetVolatile { SequenceVarHandleBarrier sequence = new SequenceVarHandleBarrier(0); @Actor public void writer() { sequence.setVolatile(0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * Updates to non-volatile long values in Java are issued as two separate 32-bit writes. * SequenceVarHandleBarrier should store its underlying value as a volatile long and therefore should not experience this effect. */ @JCStressTest @Outcome(id = "0", expect = ACCEPTABLE, desc = "Seeing the default value: writer had not acted yet.") @Outcome(id = "-1", expect = ACCEPTABLE, desc = "Seeing the full value.") @Outcome(expect = FORBIDDEN, desc = "Other cases are forbidden.") @Ref("https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.7") @State public static class LongFullCompareAndSet { SequenceVarHandleBarrier sequence = new SequenceVarHandleBarrier(0); @Actor public void writer() { sequence.compareAndSet(0, 0xFFFFFFFF_FFFFFFFFL); } @Actor public void reader(final J_Result r) { r.r1 = sequence.get(); } } /** * In absence of synchronization, the order of independent reads is undefined. * In our case, the value in SequenceVarHandleBarrier is volatile which mandates the writes to the same * variable to be observed in a total order (that implies that _observers_ are also ordered) */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Doing first read early, not surprising.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Violates coherence.") @State public static class SameVolatileRead { private final Holder h1 = new Holder(); private final Holder h2 = h1; private static class Holder { SequenceVarHandleBarrier sequence = new SequenceVarHandleBarrier(0); } @Actor public void actor1() { h1.sequence.set(1); } @Actor public void actor2(final JJ_Result r) { Holder h1 = this.h1; Holder h2 = this.h2; r.r1 = h1.sequence.get(); r.r2 = h2.sequence.get(); } } /** * The value field in SequenceVarHandleBarrier is volatile so we should never see an update to it without seeing the update to a * previously set value also. * *

If the value was not volatile there would be no ordering rules stopping it being seen updated before the * other value. */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Caught in the middle: $x is visible, $y is not.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Seeing $y, but not $x!") @State public static class SetVolatileGuard { long x = 0; SequenceVarHandleBarrier y = new SequenceVarHandleBarrier(0); @Actor public void actor1() { x = 1; y.setVolatile(1); } @Actor public void actor2(final JJ_Result r) { r.r1 = y.get(); r.r2 = x; } } /** * The value field in SequenceVarHandleBarrier is volatile so we should never see an update to it without seeing the update to a * previously set value also. * *

If the value was not volatile there would be no ordering rules stopping it being seen updated before the * other value. * *

This is a property of the field, not a property of the method used to set the value of it. */ @JCStressTest @Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Doing both reads early.") @Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Doing both reads late.") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Caught in the middle: $x is visible, $y is not.") @Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Seeing $y, but not $x!") @State public static class SetGuard { long x = 0; SequenceVarHandleBarrier y = new SequenceVarHandleBarrier(0); @Actor public void actor1() { x = 1; y.set(1); } @Actor public void actor2(final JJ_Result r) { r.r1 = y.get(); r.r2 = x; } } /** * Volatile setting will experience total ordering. */ @JCStressTest @Outcome(id = {"0, 1", "1, 0", "1, 1"}, expect = ACCEPTABLE, desc = "Trivial under sequential consistency") @Outcome(id = "0, 0", expect = FORBIDDEN, desc = "Violates sequential consistency") @State public static class SetVolatileDekker { SequenceVarHandleBarrier x = new SequenceVarHandleBarrier(0); SequenceVarHandleBarrier y = new SequenceVarHandleBarrier(0); @Actor public void actor1(final JJ_Result r) { x.setVolatile(1); r.r1 = y.get(); } @Actor public void actor2(final JJ_Result r) { y.setVolatile(1); r.r2 = x.get(); } } /** * Non-volatile setting will not experience total ordering, those gets can be re-ordered and happen before either set. */ @JCStressTest @Outcome(id = {"0, 1", "1, 0", "1, 1"}, expect = ACCEPTABLE, desc = "Trivial under sequential consistency") @Outcome(id = "0, 0", expect = ACCEPTABLE_INTERESTING, desc = "Violates sequential consistency") @State public static class SetDekker { SequenceVarHandleBarrier x = new SequenceVarHandleBarrier(0); SequenceVarHandleBarrier y = new SequenceVarHandleBarrier(0); @Actor public void actor1(final JJ_Result r) { x.set(1); r.r1 = y.get(); } @Actor public void actor2(final JJ_Result r) { y.set(1); r.r2 = x.get(); } } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/ArrayAccessBenchmark.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.util.SimpleEvent; import com.lmax.disruptor.util.UnsafeAccess; import net.openhft.affinity.Affinity; import net.openhft.affinity.AffinityLock; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import sun.misc.Unsafe; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static java.util.function.Predicate.not; @SuppressWarnings("unused") @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Fork(2) @Threads(1) @State(Scope.Thread) public class ArrayAccessBenchmark { // To run this on a tuned system with benchmark threads pinned to isolated cpus: // Run the JMH process with an env var defining the isolated cpu list, e.g. ISOLATED_CPUS=38,40,42,44,46,48 java -jar disruptor-jmh.jar private static final List ISOLATED_CPUS = Arrays.stream(System.getenv().getOrDefault("ISOLATED_CPUS", "").split(",")) .map(String::trim) .filter(not(String::isBlank)) .map(Integer::valueOf) .collect(Collectors.toList()); private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); @State(Scope.Thread) public static class ThreadPinningState { int threadId = THREAD_COUNTER.getAndIncrement(); private AffinityLock affinityLock; @Setup public void setup() { if (ISOLATED_CPUS.size() > 0) { if (threadId > ISOLATED_CPUS.size()) { throw new IllegalArgumentException( String.format("Benchmark uses at least %d threads, only defined %d isolated cpus", threadId, ISOLATED_CPUS.size() )); } final Integer cpuId = ISOLATED_CPUS.get(threadId); affinityLock = AffinityLock.acquireLock(cpuId); System.out.printf("Attempted to set thread affinity for %s to %d, success = %b%n", Thread.currentThread().getName(), cpuId, affinityLock.isAllocated() ); } else { System.err.printf("ISOLATED_CPUS environment variable not defined, running thread %s (id=%d) on scheduler-defined CPU:%d%n ", Thread.currentThread().getName(), threadId, Affinity.getCpu()); } } @TearDown public void teardown() { if (ISOLATED_CPUS.size() > 0) { affinityLock.release(); } } } private static final int EVENT_COUNT = 64; private static final int INDEX_MASK = EVENT_COUNT - 1; private final Object[] entries = new Object[EVENT_COUNT]; public int sequence; private static final Unsafe UNSAFE = UnsafeAccess.getUnsafe(); private final int scale = UNSAFE.arrayIndexScale(Object[].class); private final int offset = UNSAFE.arrayBaseOffset(Object[].class); private final VarHandle varHandle = MethodHandles.arrayElementVarHandle(Object[].class); private final MethodHandle methodHandle = MethodHandles.arrayElementGetter(Object[].class); @Setup public void setup() { for (int i = 0; i < EVENT_COUNT; i++) { SimpleEvent simpleEvent = new SimpleEvent(); simpleEvent.setValue(i); entries[i] = simpleEvent; } sequence = 0; } @Benchmark public Object standardArrayAccess(final ThreadPinningState t) { return entries[getNextSequence()]; } @Benchmark public Object unsafeArrayAccess(final ThreadPinningState t) { return UNSAFE.getObject(entries, offset + ((long) (getNextSequence()) * scale)); } @Benchmark public Object varHandleArrayAccess(final ThreadPinningState t) { return varHandle.get(entries, getNextSequence()); } @Benchmark public Object getterMethodHandleInvokeArrayAccess(final ThreadPinningState t) throws Throwable { return methodHandle.invoke(entries, getNextSequence()); } @Benchmark public Object getterMethodHandleInvokeExactArrayAccess(final ThreadPinningState t) throws Throwable { return methodHandle.invokeExact(entries, getNextSequence()); } private int getNextSequence() { return sequence++ & INDEX_MASK; } public static void main(final String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(ArrayAccessBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/BlockingQueueBenchmark.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.util.Constants; import com.lmax.disruptor.util.DaemonThreadFactory; import com.lmax.disruptor.util.SimpleEvent; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) @Fork(1) public class BlockingQueueBenchmark { private BlockingQueue arrayBlockingQueue; private volatile boolean consumerRunning; private SimpleEvent simpleEvent; @Setup public void setup(final Blackhole bh) throws InterruptedException { arrayBlockingQueue = new ArrayBlockingQueue<>(Constants.RINGBUFFER_SIZE); final CountDownLatch consumerStartedLatch = new CountDownLatch(1); final Thread eventHandler = DaemonThreadFactory.INSTANCE.newThread(() -> { consumerStartedLatch.countDown(); while (consumerRunning) { SimpleEvent event = arrayBlockingQueue.poll(); if (event != null) { bh.consume(event); } } }); consumerRunning = true; eventHandler.start(); consumerStartedLatch.await(); simpleEvent = new SimpleEvent(); simpleEvent.setValue(0); } @Benchmark public void producing() throws InterruptedException { if (!arrayBlockingQueue.offer(simpleEvent, 1, TimeUnit.SECONDS)) { throw new IllegalStateException("Queue full, benchmark should not experience backpressure"); } } @TearDown public void tearDown() { consumerRunning = false; } public static void main(final String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(BlockingQueueBenchmark.class.getSimpleName()) .forks(1) .build(); new Runner(opt).run(); } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/MultiProducerSequencerBenchmark.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.alternatives.MultiProducerSequencerUnsafe; import com.lmax.disruptor.alternatives.MultiProducerSequencerVarHandle; import net.openhft.affinity.Affinity; import net.openhft.affinity.AffinityLock; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Group; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static java.util.function.Predicate.not; @SuppressWarnings("unused") @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Fork(2) @Threads(1) public class MultiProducerSequencerBenchmark { // To run this on a tuned system with benchmark threads pinned to isolated cpus: // Run the JMH process with an env var defining the isolated cpu list, e.g. ISOLATED_CPUS=38,40,42,44,46,48 java -jar disruptor-jmh.jar private static final List ISOLATED_CPUS = Arrays.stream(System.getenv().getOrDefault("ISOLATED_CPUS", "").split(",")) .map(String::trim) .filter(not(String::isBlank)) .map(Integer::valueOf) .collect(Collectors.toList()); private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); @State(Scope.Thread) public static class ThreadPinningState { int threadId = THREAD_COUNTER.getAndIncrement(); private AffinityLock affinityLock; @Setup public void setup() { if (ISOLATED_CPUS.size() > 0) { if (threadId > ISOLATED_CPUS.size()) { throw new IllegalArgumentException( String.format("Benchmark uses at least %d threads, only defined %d isolated cpus", threadId, ISOLATED_CPUS.size() )); } final Integer cpuId = ISOLATED_CPUS.get(threadId); affinityLock = AffinityLock.acquireLock(cpuId); System.out.printf("Attempted to set thread affinity for %s to %d, success = %b%n", Thread.currentThread().getName(), cpuId, affinityLock.isAllocated() ); } else { System.err.printf("ISOLATED_CPUS environment variable not defined, running thread %s (id=%d) on scheduler-defined CPU:%d%n ", Thread.currentThread().getName(), threadId, Affinity.getCpu()); } } @TearDown public void teardown() { if (ISOLATED_CPUS.size() > 0) { affinityLock.release(); } } } /* * com.lmax.disruptor.alternatives.MultiProducerSequencerUnsafe (as of disruptor v3.4.2) */ @State(Scope.Group) public static class StateMultiProducerSequencerUnsafe { Sequencer value1 = new MultiProducerSequencerUnsafe(64, new BlockingWaitStrategy()); Sequencer value2 = new MultiProducerSequencerUnsafe(64, new BlockingWaitStrategy()); } @Benchmark @Group("SequenceUnsafe") public boolean read1(final StateMultiProducerSequencerUnsafe s, final ThreadPinningState t) { return s.value1.isAvailable(1); } @Benchmark @Group("SequenceUnsafe") public boolean read2(final StateMultiProducerSequencerUnsafe s, final ThreadPinningState t) { return s.value1.isAvailable(1); } @Benchmark @Group("SequenceUnsafe") public void setValue1A(final StateMultiProducerSequencerUnsafe s, final ThreadPinningState t) { s.value1.publish(1L); } @Benchmark @Group("SequenceUnsafe") public void setValue1B(final StateMultiProducerSequencerUnsafe s, final ThreadPinningState t) { s.value1.publish(2L); } @Benchmark @Group("SequenceUnsafe") public void setValue2A(final StateMultiProducerSequencerUnsafe s, final ThreadPinningState t) { s.value2.publish(1L); } @Benchmark @Group("SequenceUnsafe") public void setValue2B(final StateMultiProducerSequencerUnsafe s, final ThreadPinningState t) { s.value2.publish(2L); } /* * com.lmax.disruptor.alternatives.StateSequenceVarHandle (as of disruptor v3.4.2) */ @State(Scope.Group) public static class StateMultiProducerSequencerVarHandle { Sequencer value1 = new MultiProducerSequencerVarHandle(64, new BlockingWaitStrategy()); Sequencer value2 = new MultiProducerSequencerVarHandle(64, new BlockingWaitStrategy()); } @Benchmark @Group("StateMultiProducerSequencerVarHandle") public boolean read1(final StateMultiProducerSequencerVarHandle s, final ThreadPinningState t) { return s.value1.isAvailable(1); } @Benchmark @Group("StateMultiProducerSequencerVarHandle") public boolean read2(final StateMultiProducerSequencerVarHandle s, final ThreadPinningState t) { return s.value1.isAvailable(1); } @Benchmark @Group("StateMultiProducerSequencerVarHandle") public void setValue1A(final StateMultiProducerSequencerVarHandle s, final ThreadPinningState t) { s.value1.publish(1L); } @Benchmark @Group("StateMultiProducerSequencerVarHandle") public void setValue1B(final StateMultiProducerSequencerVarHandle s, final ThreadPinningState t) { s.value1.publish(2L); } @Benchmark @Group("StateMultiProducerSequencerVarHandle") public void setValue2A(final StateMultiProducerSequencerVarHandle s, final ThreadPinningState t) { s.value2.publish(1L); } @Benchmark @Group("StateMultiProducerSequencerVarHandle") public void setValue2B(final StateMultiProducerSequencerVarHandle s, final ThreadPinningState t) { s.value2.publish(2L); } public static void main(final String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(MultiProducerSequencerBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/MultiProducerSingleConsumer.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.util.DaemonThreadFactory; import com.lmax.disruptor.util.SimpleEvent; import com.lmax.disruptor.util.SimpleEventHandler; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OperationsPerInvocation; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) @Fork(1) public class MultiProducerSingleConsumer { private RingBuffer ringBuffer; private Disruptor disruptor; private static final int BIG_BUFFER = 1 << 22; private static final int BATCH_SIZE = 100; @Setup public void setup(final Blackhole bh) { disruptor = new Disruptor<>(SimpleEvent::new, BIG_BUFFER, DaemonThreadFactory.INSTANCE, ProducerType.MULTI, new BusySpinWaitStrategy()); disruptor.handleEventsWith(new SimpleEventHandler(bh)); ringBuffer = disruptor.start(); } @Benchmark @Threads(4) public void producing() { long sequence = ringBuffer.next(); SimpleEvent simpleEvent = ringBuffer.get(sequence); simpleEvent.setValue(0); ringBuffer.publish(sequence); } @Benchmark @Threads(4) @OperationsPerInvocation(BATCH_SIZE) public void producingBatch() { long hi = ringBuffer.next(BATCH_SIZE); long lo = hi - (BATCH_SIZE - 1); for (long sequence = lo; sequence <= hi; sequence++) { SimpleEvent simpleEvent = ringBuffer.get(sequence); simpleEvent.setValue(0); } ringBuffer.publish(lo, hi); } @TearDown public void tearDown() { disruptor.shutdown(); } public static void main(final String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(MultiProducerSingleConsumer.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/RingBufferBenchmark.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.alternatives.RingBufferArray; import com.lmax.disruptor.alternatives.RingBufferUnsafe; import com.lmax.disruptor.support.DummyWaitStrategy; import com.lmax.disruptor.support.StubEvent; import net.openhft.affinity.Affinity; import net.openhft.affinity.AffinityLock; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Group; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static java.util.function.Predicate.not; @SuppressWarnings("ALL") @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Fork(2) @Threads(1) public class RingBufferBenchmark { // To run this on a tuned system with benchmark threads pinned to isolated cpus: // Run the JMH process with an env var defining the isolated cpu list, e.g. ISOLATED_CPUS=38,40,42,44,46,48 java -jar disruptor-jmh.jar private static final List ISOLATED_CPUS = Arrays.stream(System.getenv().getOrDefault("ISOLATED_CPUS", "").split(",")) .map(String::trim) .filter(not(String::isBlank)) .map(Integer::valueOf) .collect(Collectors.toList()); private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); @State(Scope.Thread) public static class ThreadPinningState { int threadId = THREAD_COUNTER.getAndIncrement(); private AffinityLock affinityLock; @Setup public void setup() { if (ISOLATED_CPUS.size() > 0) { if (threadId > ISOLATED_CPUS.size()) { throw new IllegalArgumentException( String.format("Benchmark uses at least %d threads, only defined %d isolated cpus", threadId, ISOLATED_CPUS.size() )); } final Integer cpuId = ISOLATED_CPUS.get(threadId); affinityLock = AffinityLock.acquireLock(cpuId); System.out.printf("Attempted to set thread affinity for %s to %d, success = %b%n", Thread.currentThread().getName(), cpuId, affinityLock.isAllocated() ); } else { System.err.printf("ISOLATED_CPUS environment variable not defined, running thread %s (id=%d) on scheduler-defined CPU:%d%n ", Thread.currentThread().getName(), threadId, Affinity.getCpu()); } } @TearDown public void teardown() { if (ISOLATED_CPUS.size() > 0) { affinityLock.release(); } } } /* * APPROACH 1: RingBufferUnsafe - Using the unsafe API to avoid bounds-checking, as was the case for Disruptor 3.x */ @State(Scope.Group) public static class StateRingBufferUnsafe { RingBufferUnsafe ringBufferUnsafe = new RingBufferUnsafe<>( () -> new StubEvent(-1), new SingleProducerSequencer(128, new DummyWaitStrategy())); } @Benchmark @Group("RingBufferUnsafe") public Object readUnsafe(final StateRingBufferUnsafe ringBufferUnsafe, final ThreadPinningState t) { return ringBufferUnsafe.ringBufferUnsafe.get(64); } @Benchmark @Group("RingBufferUnsafe") public void writeUnsafe(final StateRingBufferUnsafe ringBufferUnsafe, final ThreadPinningState t) { ringBufferUnsafe.ringBufferUnsafe.publish(64); } /* * APPROACH 2: RingBufferArray - There is no support for non-bounds-checked array element access as there was via * unsafe. So the simplest approach is to go back to a plain array and index to elements. */ @State(Scope.Group) public static class StateRingBufferArray { RingBufferArray ringBufferVarHandle = new RingBufferArray<>( () -> new StubEvent(-1), new SingleProducerSequencer(128, new DummyWaitStrategy()) ); } @Benchmark @Group("RingBufferArray") public Object readArray(final StateRingBufferArray ringBufferVarHandle, final ThreadPinningState t) { return ringBufferVarHandle.ringBufferVarHandle.get(64); } @Benchmark @Group("RingBufferArray") public void writeArray(final StateRingBufferArray ringBufferVarHandle, final ThreadPinningState t) { ringBufferVarHandle.ringBufferVarHandle.publish(64); } public static void main(final String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(RingBufferBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/RingBufferFalseSharingBenchmark.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.util.SimpleEvent; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Group; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; /* * Based on false-sharing benchmark in open JDK * @see https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_22_FalseSharing.java * */ @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(5) public class RingBufferFalseSharingBenchmark { /* * We take advantage of the inheritance trick used in RingBuffer * to create an object without the padding that occur after the fields. * * Java object layout using JDK15: * com.lmax.disruptor.RingBufferFalseSharingBenchmark$HalfPaddedRingBufferWithNoisyNeighbour object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 1 byte RingBufferPad.p77 N/A 13 1 byte RingBufferPad.p11 N/A 14 1 byte RingBufferPad.p12 N/A 15 1 byte RingBufferPad.p13 N/A 16 1 byte RingBufferPad.p14 N/A 17 1 byte RingBufferPad.p15 N/A 18 1 byte RingBufferPad.p16 N/A 19 1 byte RingBufferPad.p17 N/A 20 1 byte RingBufferPad.p20 N/A 21 1 byte RingBufferPad.p21 N/A 22 1 byte RingBufferPad.p22 N/A 23 1 byte RingBufferPad.p23 N/A 24 1 byte RingBufferPad.p24 N/A 25 1 byte RingBufferPad.p25 N/A 26 1 byte RingBufferPad.p26 N/A 27 1 byte RingBufferPad.p27 N/A 28 1 byte RingBufferPad.p30 N/A 29 1 byte RingBufferPad.p31 N/A 30 1 byte RingBufferPad.p32 N/A 31 1 byte RingBufferPad.p33 N/A 32 1 byte RingBufferPad.p34 N/A 33 1 byte RingBufferPad.p35 N/A 34 1 byte RingBufferPad.p36 N/A 35 1 byte RingBufferPad.p37 N/A 36 1 byte RingBufferPad.p40 N/A 37 1 byte RingBufferPad.p41 N/A 38 1 byte RingBufferPad.p42 N/A 39 1 byte RingBufferPad.p43 N/A 40 1 byte RingBufferPad.p44 N/A 41 1 byte RingBufferPad.p45 N/A 42 1 byte RingBufferPad.p46 N/A 43 1 byte RingBufferPad.p47 N/A 44 1 byte RingBufferPad.p50 N/A 45 1 byte RingBufferPad.p51 N/A 46 1 byte RingBufferPad.p52 N/A 47 1 byte RingBufferPad.p53 N/A 48 1 byte RingBufferPad.p54 N/A 49 1 byte RingBufferPad.p55 N/A 50 1 byte RingBufferPad.p56 N/A 51 1 byte RingBufferPad.p57 N/A 52 1 byte RingBufferPad.p60 N/A 53 1 byte RingBufferPad.p61 N/A 54 1 byte RingBufferPad.p62 N/A 55 1 byte RingBufferPad.p63 N/A 56 1 byte RingBufferPad.p64 N/A 57 1 byte RingBufferPad.p65 N/A 58 1 byte RingBufferPad.p66 N/A 59 1 byte RingBufferPad.p67 N/A 60 1 byte RingBufferPad.p70 N/A 61 1 byte RingBufferPad.p71 N/A 62 1 byte RingBufferPad.p72 N/A 63 1 byte RingBufferPad.p73 N/A 64 1 byte RingBufferPad.p74 N/A 65 1 byte RingBufferPad.p75 N/A 66 1 byte RingBufferPad.p76 N/A 67 1 byte RingBufferPad.p10 N/A 68 4 int RingBufferFields.bufferSize N/A 72 8 long RingBufferFields.indexMask N/A 80 4 java.lang.Object[] RingBufferFields.entries N/A 84 4 com.lmax.disruptor.Sequencer RingBufferFields.sequencer N/A 88 4 int HalfPaddedRingBufferWithNoisyNeighbour.writeOnly N/A 92 4 (loss due to the next object alignment) Instance size: 96 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total */ @State(Scope.Group) public static class HalfPaddedRingBufferWithNoisyNeighbour extends RingBufferFields { int writeOnly; public HalfPaddedRingBufferWithNoisyNeighbour() { super(SimpleEvent::new, new SingleProducerSequencer(16, new BusySpinWaitStrategy())); } } @Benchmark @Group("halfpadded") public int reader(final HalfPaddedRingBufferWithNoisyNeighbour s) { return s.bufferSize; } @Benchmark @Group("halfpadded") public void writer(final HalfPaddedRingBufferWithNoisyNeighbour s) { s.writeOnly++; } /* * A fully padded RingBuffer using longs */ @State(Scope.Group) public static class PaddedRingBuffer extends RingBufferFields { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; public PaddedRingBuffer() { super(SimpleEvent::new, new SingleProducerSequencer(16, new BusySpinWaitStrategy())); } } /* Java object layout using JDK15: com.lmax.disruptor.RingBufferFalseSharingBenchmark$PaddedRingBufferWithNoisyNeighbour object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 1 byte RingBufferPad.p77 N/A 13 1 byte RingBufferPad.p11 N/A 14 1 byte RingBufferPad.p12 N/A 15 1 byte RingBufferPad.p13 N/A 16 1 byte RingBufferPad.p14 N/A 17 1 byte RingBufferPad.p15 N/A 18 1 byte RingBufferPad.p16 N/A 19 1 byte RingBufferPad.p17 N/A 20 1 byte RingBufferPad.p20 N/A 21 1 byte RingBufferPad.p21 N/A 22 1 byte RingBufferPad.p22 N/A 23 1 byte RingBufferPad.p23 N/A 24 1 byte RingBufferPad.p24 N/A 25 1 byte RingBufferPad.p25 N/A 26 1 byte RingBufferPad.p26 N/A 27 1 byte RingBufferPad.p27 N/A 28 1 byte RingBufferPad.p30 N/A 29 1 byte RingBufferPad.p31 N/A 30 1 byte RingBufferPad.p32 N/A 31 1 byte RingBufferPad.p33 N/A 32 1 byte RingBufferPad.p34 N/A 33 1 byte RingBufferPad.p35 N/A 34 1 byte RingBufferPad.p36 N/A 35 1 byte RingBufferPad.p37 N/A 36 1 byte RingBufferPad.p40 N/A 37 1 byte RingBufferPad.p41 N/A 38 1 byte RingBufferPad.p42 N/A 39 1 byte RingBufferPad.p43 N/A 40 1 byte RingBufferPad.p44 N/A 41 1 byte RingBufferPad.p45 N/A 42 1 byte RingBufferPad.p46 N/A 43 1 byte RingBufferPad.p47 N/A 44 1 byte RingBufferPad.p50 N/A 45 1 byte RingBufferPad.p51 N/A 46 1 byte RingBufferPad.p52 N/A 47 1 byte RingBufferPad.p53 N/A 48 1 byte RingBufferPad.p54 N/A 49 1 byte RingBufferPad.p55 N/A 50 1 byte RingBufferPad.p56 N/A 51 1 byte RingBufferPad.p57 N/A 52 1 byte RingBufferPad.p60 N/A 53 1 byte RingBufferPad.p61 N/A 54 1 byte RingBufferPad.p62 N/A 55 1 byte RingBufferPad.p63 N/A 56 1 byte RingBufferPad.p64 N/A 57 1 byte RingBufferPad.p65 N/A 58 1 byte RingBufferPad.p66 N/A 59 1 byte RingBufferPad.p67 N/A 60 1 byte RingBufferPad.p70 N/A 61 1 byte RingBufferPad.p71 N/A 62 1 byte RingBufferPad.p72 N/A 63 1 byte RingBufferPad.p73 N/A 64 1 byte RingBufferPad.p74 N/A 65 1 byte RingBufferPad.p75 N/A 66 1 byte RingBufferPad.p76 N/A 67 1 byte RingBufferPad.p10 N/A 68 4 int RingBufferFields.bufferSize N/A 72 8 long RingBufferFields.indexMask N/A 80 4 java.lang.Object[] RingBufferFields.entries N/A 84 4 com.lmax.disruptor.Sequencer RingBufferFields.sequencer N/A 88 1 byte PaddedRingBuffer.p77 N/A 89 1 byte PaddedRingBuffer.p11 N/A 90 1 byte PaddedRingBuffer.p12 N/A 91 1 byte PaddedRingBuffer.p13 N/A 92 1 byte PaddedRingBuffer.p14 N/A 93 1 byte PaddedRingBuffer.p15 N/A 94 1 byte PaddedRingBuffer.p16 N/A 95 1 byte PaddedRingBuffer.p17 N/A 96 1 byte PaddedRingBuffer.p20 N/A 97 1 byte PaddedRingBuffer.p21 N/A 98 1 byte PaddedRingBuffer.p22 N/A 99 1 byte PaddedRingBuffer.p23 N/A 100 1 byte PaddedRingBuffer.p24 N/A 101 1 byte PaddedRingBuffer.p25 N/A 102 1 byte PaddedRingBuffer.p26 N/A 103 1 byte PaddedRingBuffer.p27 N/A 104 1 byte PaddedRingBuffer.p30 N/A 105 1 byte PaddedRingBuffer.p31 N/A 106 1 byte PaddedRingBuffer.p32 N/A 107 1 byte PaddedRingBuffer.p33 N/A 108 1 byte PaddedRingBuffer.p34 N/A 109 1 byte PaddedRingBuffer.p35 N/A 110 1 byte PaddedRingBuffer.p36 N/A 111 1 byte PaddedRingBuffer.p37 N/A 112 1 byte PaddedRingBuffer.p40 N/A 113 1 byte PaddedRingBuffer.p41 N/A 114 1 byte PaddedRingBuffer.p42 N/A 115 1 byte PaddedRingBuffer.p43 N/A 116 1 byte PaddedRingBuffer.p44 N/A 117 1 byte PaddedRingBuffer.p45 N/A 118 1 byte PaddedRingBuffer.p46 N/A 119 1 byte PaddedRingBuffer.p47 N/A 120 1 byte PaddedRingBuffer.p50 N/A 121 1 byte PaddedRingBuffer.p51 N/A 122 1 byte PaddedRingBuffer.p52 N/A 123 1 byte PaddedRingBuffer.p53 N/A 124 1 byte PaddedRingBuffer.p54 N/A 125 1 byte PaddedRingBuffer.p55 N/A 126 1 byte PaddedRingBuffer.p56 N/A 127 1 byte PaddedRingBuffer.p57 N/A 128 1 byte PaddedRingBuffer.p60 N/A 129 1 byte PaddedRingBuffer.p61 N/A 130 1 byte PaddedRingBuffer.p62 N/A 131 1 byte PaddedRingBuffer.p63 N/A 132 1 byte PaddedRingBuffer.p64 N/A 133 1 byte PaddedRingBuffer.p65 N/A 134 1 byte PaddedRingBuffer.p66 N/A 135 1 byte PaddedRingBuffer.p67 N/A 136 1 byte PaddedRingBuffer.p70 N/A 137 1 byte PaddedRingBuffer.p71 N/A 138 1 byte PaddedRingBuffer.p72 N/A 139 1 byte PaddedRingBuffer.p73 N/A 140 1 byte PaddedRingBuffer.p74 N/A 141 1 byte PaddedRingBuffer.p75 N/A 142 1 byte PaddedRingBuffer.p76 N/A 143 1 byte PaddedRingBuffer.p10 N/A 144 4 int PaddedRingBufferWithNoisyNeighbour.writeOnly N/A 148 4 (loss due to the next object alignment) Instance size: 152 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total */ @State(Scope.Group) public static class PaddedRingBufferWithNoisyNeighbour extends PaddedRingBuffer { int writeOnly; } @Benchmark @Group("padded") public int reader(final PaddedRingBufferWithNoisyNeighbour s) { return s.bufferSize; } @Benchmark @Group("padded") public void writer(final PaddedRingBufferWithNoisyNeighbour s) { s.writeOnly++; } public static void main(final String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(RingBufferFalseSharingBenchmark.class.getSimpleName()) .threads(Runtime.getRuntime().availableProcessors()) .build(); new Runner(opt).run(); } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/SequenceBenchmark.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.alternatives.SequenceDoublePadded; import com.lmax.disruptor.alternatives.SequenceUnsafe; import com.lmax.disruptor.alternatives.SequenceVarHandle; import com.lmax.disruptor.alternatives.SequenceVarHandleArray; import com.lmax.disruptor.alternatives.SequenceVarHandleBarrier; import net.openhft.affinity.Affinity; import net.openhft.affinity.AffinityLock; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Group; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import static java.util.function.Predicate.not; @SuppressWarnings("ALL") @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Fork(2) @Threads(1) public class SequenceBenchmark { // To run this on a tuned system with benchmark threads pinned to isolated cpus: // Run the JMH process with an env var defining the isolated cpu list, e.g. ISOLATED_CPUS=38,40,42,44,46,48 java -jar disruptor-jmh.jar private static final List ISOLATED_CPUS = Arrays.stream(System.getenv().getOrDefault("ISOLATED_CPUS", "").split(",")) .map(String::trim) .filter(not(String::isBlank)) .map(Integer::valueOf) .collect(Collectors.toList()); private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); @State(Scope.Thread) public static class ThreadPinningState { int threadId = THREAD_COUNTER.getAndIncrement(); private AffinityLock affinityLock; @Setup public void setup() { if (ISOLATED_CPUS.size() > 0) { if (threadId > ISOLATED_CPUS.size()) { throw new IllegalArgumentException( String.format("Benchmark uses at least %d threads, only defined %d isolated cpus", threadId, ISOLATED_CPUS.size() )); } final Integer cpuId = ISOLATED_CPUS.get(threadId); affinityLock = AffinityLock.acquireLock(cpuId); System.out.printf("Attempted to set thread affinity for %s to %d, success = %b%n", Thread.currentThread().getName(), cpuId, affinityLock.isAllocated() ); } else { System.err.printf("ISOLATED_CPUS environment variable not defined, running thread %s (id=%d) on scheduler-defined CPU:%d%n ", Thread.currentThread().getName(), threadId, Affinity.getCpu()); } } @TearDown public void teardown() { if (ISOLATED_CPUS.size() > 0) { affinityLock.release(); } } } /* * APPROACH 1: AtomicLong * * Thread safe? Check. Atomic updates? Check. */ @State(Scope.Group) public static class StateAtomic { AtomicLong value1 = new AtomicLong(0); AtomicLong value2 = new AtomicLong(0); } @Benchmark @Group("AtomicLong") public long read1(final StateAtomic s, final ThreadPinningState t) { return s.value1.get(); } @Benchmark @Group("AtomicLong") public long read2(final StateAtomic s, final ThreadPinningState t) { return s.value2.get(); } @Benchmark @Group("AtomicLong") public void setValue1Opaque(final StateAtomic s, final ThreadPinningState t) { // Put Long Opaque s.value1.setOpaque(1234L); } @Benchmark @Group("AtomicLong") public void setValue1Volatile(final StateAtomic s, final ThreadPinningState t) { // Put Long Volatile s.value1.set(5678L); } @Benchmark @Group("AtomicLong") public long incrementValue2(final StateAtomic s, final ThreadPinningState t) { return s.value2.getAndIncrement(); } /* * APPROACH 2: com.lmax.disruptor.Sequence (as of disruptor v3.4.2) * * A lot like AtomicLong, but with some padding to avoid false sharing. * This uses UNSAFE to give us more control over the memory model of the field, we don't always need full volatile * guarantees and we need to use compareAndSwap to be atomic. */ @State(Scope.Group) public static class StateSequenceUnsafe { SequenceUnsafe value1 = new SequenceUnsafe(0); SequenceUnsafe value2 = new SequenceUnsafe(0); } @Benchmark @Group("SequenceUnsafe") public long read1(final StateSequenceUnsafe s, final ThreadPinningState t) { return s.value1.get(); } @Benchmark @Group("SequenceUnsafe") public long read2(final StateSequenceUnsafe s, final ThreadPinningState t) { return s.value2.get(); } @Benchmark @Group("SequenceUnsafe") public void setValue1(final StateSequenceUnsafe s, final ThreadPinningState t) { // Put Ordered Long s.value1.set(1234L); } @Benchmark @Group("SequenceUnsafe") public void setValue1Volatile(final StateSequenceUnsafe s, final ThreadPinningState t) { // Put Long Volatile s.value1.setVolatile(5678L); } @Benchmark @Group("SequenceUnsafe") public long incrementValue2(final StateSequenceUnsafe s, final ThreadPinningState t) { return s.value2.incrementAndGet(); } /* * APPROACH 2.5: com.lmax.disruptor.alternatives.SequenceDoublePadded * * This is identical to the Sequence from Disruptor 3.4.2 but with double the amount of padding. * https://github.com/LMAX-Exchange/disruptor/issues/231 raised the point of Intel CPUs optionally (on by default I * believe) prefetching 2 cache lines. * * This benchmark should show if there is any difference in performance having extra padding when compared to the * regular Sequence benchmark. */ @State(Scope.Group) public static class StateSequenceDoublePadded { SequenceDoublePadded value1 = new SequenceDoublePadded(0); SequenceDoublePadded value2 = new SequenceDoublePadded(0); } @Benchmark @Group("SequenceDoublePadded") public long read1(final StateSequenceDoublePadded s, final ThreadPinningState t) { return s.value1.get(); } @Benchmark @Group("SequenceDoublePadded") public long read2(final StateSequenceDoublePadded s, final ThreadPinningState t) { return s.value2.get(); } @Benchmark @Group("SequenceDoublePadded") public void setValue1(final StateSequenceDoublePadded s, final ThreadPinningState t) { // Put Ordered Long s.value1.set(1234L); } @Benchmark @Group("SequenceDoublePadded") public void setValue1Volatile(final StateSequenceDoublePadded s, final ThreadPinningState t) { // Put Long Volatile s.value1.setVolatile(5678L); } @Benchmark @Group("SequenceDoublePadded") public long incrementValue2(final StateSequenceDoublePadded s, final ThreadPinningState t) { return s.value2.incrementAndGet(); } /* * APPROACH 3: com.lmax.disruptor.alternatives.SequenceVarHandle * * An updated version of com.lmax.disruptor.Sequence but using VarHandle instead of UNSAFE to get memory ordering. * This is probably the way we should go for version Disruptor 4.0 */ @State(Scope.Group) public static class StateSequenceVarHandle { SequenceVarHandle value1 = new SequenceVarHandle(0); SequenceVarHandle value2 = new SequenceVarHandle(0); } @Benchmark @Group("SequenceVarHandle") public long read1(final StateSequenceVarHandle s, final ThreadPinningState t) { return s.value1.get(); } @Benchmark @Group("SequenceVarHandle") public long read2(final StateSequenceVarHandle s, final ThreadPinningState t) { return s.value2.get(); } @Benchmark @Group("SequenceVarHandle") public void setValue1(final StateSequenceVarHandle s, final ThreadPinningState t) { // Put Ordered Long s.value1.set(1234L); } @Benchmark @Group("SequenceVarHandle") public void setValue1Volatile(final StateSequenceVarHandle s, final ThreadPinningState t) { // Put Long Volatile s.value1.setVolatile(5678L); } @Benchmark @Group("SequenceVarHandle") public long incrementValue2(final StateSequenceVarHandle s, final ThreadPinningState t) { return s.value2.incrementAndGet(); } /* * APPROACH 3.5: com.lmax.disruptor.alternatives.SequenceVarHandleBarrier * * Much like the VarHandle version but with manual memory barriers used. * We think this might cut down on some boxing and maybe gives a little more flexibility. */ @State(Scope.Group) public static class StateSequenceVarHandleBarrier { SequenceVarHandleBarrier value1 = new SequenceVarHandleBarrier(0); SequenceVarHandleBarrier value2 = new SequenceVarHandleBarrier(0); } @Benchmark @Group("SequenceVarHandleBarrier") public long read1(final StateSequenceVarHandleBarrier s, final ThreadPinningState t) { return s.value1.get(); } @Benchmark @Group("SequenceVarHandleBarrier") public long read2(final StateSequenceVarHandleBarrier s, final ThreadPinningState t) { return s.value2.get(); } @Benchmark @Group("SequenceVarHandleBarrier") public void setValue1(final StateSequenceVarHandleBarrier s, final ThreadPinningState t) { // Put Ordered Long s.value1.set(1234L); } @Benchmark @Group("SequenceVarHandleBarrier") public void setValue1Volatile(final StateSequenceVarHandleBarrier s, final ThreadPinningState t) { // Put Long Volatile s.value1.setVolatile(5678L); } @Benchmark @Group("SequenceVarHandleBarrier") public long incrementValue2(final StateSequenceVarHandleBarrier s, final ThreadPinningState t) { return s.value2.incrementAndGet(); } /* * APPROACH 4: com.lmax.disruptor.alternatives.SequenceVarHandleArray * * Similar to the SequenceVarHandle but instead of using class hierarchy for padding, using a long array. * This seemed like a good idea but suffers from array bounds checking slowing down all the operations. * This method probably isn't a good way to go, but kept here as a warning to others who think this is a good way to * do cache-line padding. */ @State(Scope.Group) public static class StateSequenceVarHandleArray { SequenceVarHandleArray value1 = new SequenceVarHandleArray(0); SequenceVarHandleArray value2 = new SequenceVarHandleArray(0); } @Benchmark @Group("SequenceVarHandleArray") public long read1(final StateSequenceVarHandleArray s, final ThreadPinningState t) { return s.value1.get(); } @Benchmark @Group("SequenceVarHandleArray") public long read2(final StateSequenceVarHandleArray s, final ThreadPinningState t) { return s.value2.get(); } @Benchmark @Group("SequenceVarHandleArray") public void setValue1(final StateSequenceVarHandleArray s, final ThreadPinningState t) { // Put Ordered Long s.value1.set(1234L); } @Benchmark @Group("SequenceVarHandleArray") public void setValue1Volatile(final StateSequenceVarHandleArray s, final ThreadPinningState t) { // Put Long Volatile s.value1.setVolatile(5678L); } @Benchmark @Group("SequenceVarHandleArray") public long incrementValue2(final StateSequenceVarHandleArray s, final ThreadPinningState t) { return s.value2.incrementAndGet(); } public static void main(final String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(SequenceBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/SingleProducerSingleConsumer.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.util.Constants; import com.lmax.disruptor.util.DaemonThreadFactory; import com.lmax.disruptor.util.SimpleEvent; import com.lmax.disruptor.util.SimpleEventHandler; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) @Fork(1) public class SingleProducerSingleConsumer { private RingBuffer ringBuffer; private Disruptor disruptor; @Setup public void setup(final Blackhole bh) { disruptor = new Disruptor<>(SimpleEvent::new, Constants.RINGBUFFER_SIZE, DaemonThreadFactory.INSTANCE, ProducerType.SINGLE, new BusySpinWaitStrategy()); disruptor.handleEventsWith(new SimpleEventHandler(bh)); ringBuffer = disruptor.start(); } @Benchmark public void producing() { long sequence = ringBuffer.next(); SimpleEvent simpleEvent = ringBuffer.get(sequence); simpleEvent.setValue(0); ringBuffer.publish(sequence); } @TearDown public void tearDown() { disruptor.shutdown(); } public static void main(final String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(SingleProducerSingleConsumer.class.getSimpleName()) .forks(1) .build(); new Runner(opt).run(); } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/util/Constants.java ================================================ package com.lmax.disruptor.util; public class Constants { public static final int RINGBUFFER_SIZE = 1 << 20; public static final int ITERATIONS = 1_000_000; } ================================================ FILE: src/jmh/java/com/lmax/disruptor/util/SimpleEvent.java ================================================ package com.lmax.disruptor.util; public class SimpleEvent { private long value = Long.MIN_VALUE; public long getValue() { return value; } public void setValue(final long value) { this.value = value; } @Override public String toString() { return "SimpleEvent{" + "value=" + value + '}'; } } ================================================ FILE: src/jmh/java/com/lmax/disruptor/util/SimpleEventHandler.java ================================================ package com.lmax.disruptor.util; import com.lmax.disruptor.EventHandler; import org.openjdk.jmh.infra.Blackhole; public class SimpleEventHandler implements EventHandler { private final Blackhole bh; public SimpleEventHandler(final Blackhole bh) { this.bh = bh; } @Override public void onEvent(final SimpleEvent event, final long sequence, final boolean endOfBatch) { bh.consume(event); } } ================================================ FILE: src/main/java/com/lmax/disruptor/AbstractSequencer.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.util.Util; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** * Base class for the various sequencer types (single/multi). Provides * common functionality like the management of gating sequences (add/remove) and * ownership of the current cursor. */ public abstract class AbstractSequencer implements Sequencer { private static final AtomicReferenceFieldUpdater SEQUENCE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(AbstractSequencer.class, Sequence[].class, "gatingSequences"); protected final int bufferSize; protected final WaitStrategy waitStrategy; protected final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); protected volatile Sequence[] gatingSequences = new Sequence[0]; /** * Create with the specified buffer size and wait strategy. * * @param bufferSize The total number of entries, must be a positive power of 2. * @param waitStrategy The wait strategy used by this sequencer */ public AbstractSequencer(final int bufferSize, final WaitStrategy waitStrategy) { if (bufferSize < 1) { throw new IllegalArgumentException("bufferSize must not be less than 1"); } if (Integer.bitCount(bufferSize) != 1) { throw new IllegalArgumentException("bufferSize must be a power of 2"); } this.bufferSize = bufferSize; this.waitStrategy = waitStrategy; } /** * @see Sequencer#getCursor() */ @Override public final long getCursor() { return cursor.get(); } /** * @see Sequencer#getBufferSize() */ @Override public final int getBufferSize() { return bufferSize; } /** * @see Sequencer#addGatingSequences(Sequence...) */ @Override public final void addGatingSequences(final Sequence... gatingSequences) { SequenceGroups.addSequences(this, SEQUENCE_UPDATER, this, gatingSequences); } /** * @see Sequencer#removeGatingSequence(Sequence) */ @Override public boolean removeGatingSequence(final Sequence sequence) { return SequenceGroups.removeSequence(this, SEQUENCE_UPDATER, sequence); } /** * @see Sequencer#getMinimumSequence() */ @Override public long getMinimumSequence() { return Util.getMinimumSequence(gatingSequences, cursor.get()); } /** * @see Sequencer#newBarrier(Sequence...) */ @Override public SequenceBarrier newBarrier(final Sequence... sequencesToTrack) { return new ProcessingSequenceBarrier(this, waitStrategy, cursor, sequencesToTrack); } /** * Creates an event poller for this sequence that will use the supplied data provider and * gating sequences. * * @param dataProvider The data source for users of this event poller * @param gatingSequences Sequence to be gated on. * @return A poller that will gate on this ring buffer and the supplied sequences. */ @Override public EventPoller newPoller(final DataProvider dataProvider, final Sequence... gatingSequences) { return EventPoller.newInstance(dataProvider, this, new Sequence(), cursor, gatingSequences); } @Override public String toString() { return "AbstractSequencer{" + "waitStrategy=" + waitStrategy + ", cursor=" + cursor + ", gatingSequences=" + Arrays.toString(gatingSequences) + '}'; } } ================================================ FILE: src/main/java/com/lmax/disruptor/AggregateEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * An aggregate collection of {@link EventHandler}s that get called in sequence for each event. * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. */ public final class AggregateEventHandler implements EventHandler { private final EventHandler[] eventHandlers; /** * Construct an aggregate collection of {@link EventHandler}s to be called in sequence. * * @param eventHandlers to be called in sequence. */ @SafeVarargs public AggregateEventHandler(final EventHandler... eventHandlers) { this.eventHandlers = eventHandlers; } @Override public void onEvent(final T event, final long sequence, final boolean endOfBatch) throws Exception { for (final EventHandler eventHandler : eventHandlers) { eventHandler.onEvent(event, sequence, endOfBatch); } } @Override public void onStart() { for (final EventHandler eventHandler : eventHandlers) { eventHandler.onStart(); } } @Override public void onShutdown() { for (final EventHandler eventHandler : eventHandlers) { eventHandler.onShutdown(); } } } ================================================ FILE: src/main/java/com/lmax/disruptor/AlertException.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Used to alert {@link EventProcessor}s waiting at a {@link SequenceBarrier} of status changes. * *

It does not fill in a stack trace for performance reasons. */ @SuppressWarnings({"serial", "lgtm[java/non-sync-override]"}) public final class AlertException extends Exception { /** * Pre-allocated exception to avoid garbage generation. */ public static final AlertException INSTANCE = new AlertException(); /** * Private constructor so only a single instance exists. */ private AlertException() { } /** * Overridden so the stack trace is not filled in for this exception for performance reasons. * * @return this instance. */ @Override public Throwable fillInStackTrace() { return this; } } ================================================ FILE: src/main/java/com/lmax/disruptor/BatchEventProcessor.java ================================================ /* * Copyright 2022 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import java.util.concurrent.atomic.AtomicInteger; import static com.lmax.disruptor.RewindAction.REWIND; import static java.lang.Math.min; /** * Convenience class for handling the batching semantics of consuming entries from a {@link RingBuffer} * and delegating the available events to an {@link EventHandler}. * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. */ public final class BatchEventProcessor implements EventProcessor { private static final int IDLE = 0; private static final int HALTED = IDLE + 1; private static final int RUNNING = HALTED + 1; private final AtomicInteger running = new AtomicInteger(IDLE); private ExceptionHandler exceptionHandler; private final DataProvider dataProvider; private final SequenceBarrier sequenceBarrier; private final EventHandlerBase eventHandler; private final int batchLimitOffset; private final Sequence sequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); private final RewindHandler rewindHandler; private int retriesAttempted = 0; BatchEventProcessor( final DataProvider dataProvider, final SequenceBarrier sequenceBarrier, final EventHandlerBase eventHandler, final int maxBatchSize, final BatchRewindStrategy batchRewindStrategy ) { this.dataProvider = dataProvider; this.sequenceBarrier = sequenceBarrier; this.eventHandler = eventHandler; if (maxBatchSize < 1) { throw new IllegalArgumentException("maxBatchSize must be greater than 0"); } this.batchLimitOffset = maxBatchSize - 1; this.rewindHandler = eventHandler instanceof RewindableEventHandler ? new TryRewindHandler(batchRewindStrategy) : new NoRewindHandler(); } @Override public Sequence getSequence() { return sequence; } @Override public void halt() { running.set(HALTED); sequenceBarrier.alert(); } @Override public boolean isRunning() { return running.get() != IDLE; } /** * Set a new {@link ExceptionHandler} for handling exceptions propagated out of the {@link BatchEventProcessor}. * * @param exceptionHandler to replace the existing exceptionHandler. */ public void setExceptionHandler(final ExceptionHandler exceptionHandler) { if (null == exceptionHandler) { throw new NullPointerException(); } this.exceptionHandler = exceptionHandler; } /** * It is ok to have another thread rerun this method after a halt(). * * @throws IllegalStateException if this object instance is already running in a thread */ @Override public void run() { int witnessValue = running.compareAndExchange(IDLE, RUNNING); if (witnessValue == IDLE) // Successful CAS { sequenceBarrier.clearAlert(); notifyStart(); try { if (running.get() == RUNNING) { processEvents(); } } finally { notifyShutdown(); running.set(IDLE); } } else { if (witnessValue == RUNNING) { throw new IllegalStateException("Thread is already running"); } else { earlyExit(); } } } private void processEvents() { T event = null; long nextSequence = sequence.get() + 1L; while (true) { final long startOfBatchSequence = nextSequence; try { try { final long availableSequence = sequenceBarrier.waitFor(nextSequence); final long endOfBatchSequence = min(nextSequence + batchLimitOffset, availableSequence); if (nextSequence <= endOfBatchSequence) { eventHandler.onBatchStart(endOfBatchSequence - nextSequence + 1, availableSequence - nextSequence + 1); } while (nextSequence <= endOfBatchSequence) { event = dataProvider.get(nextSequence); eventHandler.onEvent(event, nextSequence, nextSequence == endOfBatchSequence); nextSequence++; } retriesAttempted = 0; sequence.set(endOfBatchSequence); } catch (final RewindableException e) { nextSequence = rewindHandler.attemptRewindGetNextSequence(e, startOfBatchSequence); } } catch (final TimeoutException e) { notifyTimeout(sequence.get()); } catch (final AlertException ex) { if (running.get() != RUNNING) { break; } } catch (final Throwable ex) { handleEventException(ex, nextSequence, event); sequence.set(nextSequence); nextSequence++; } } } private void earlyExit() { notifyStart(); notifyShutdown(); } private void notifyTimeout(final long availableSequence) { try { eventHandler.onTimeout(availableSequence); } catch (Throwable e) { handleEventException(e, availableSequence, null); } } /** * Notifies the EventHandler when this processor is starting up. */ private void notifyStart() { try { eventHandler.onStart(); } catch (final Throwable ex) { handleOnStartException(ex); } } /** * Notifies the EventHandler immediately prior to this processor shutting down. */ private void notifyShutdown() { try { eventHandler.onShutdown(); } catch (final Throwable ex) { handleOnShutdownException(ex); } } /** * Delegate to {@link ExceptionHandler#handleEventException(Throwable, long, Object)} on the delegate or * the default {@link ExceptionHandler} if one has not been configured. */ private void handleEventException(final Throwable ex, final long sequence, final T event) { getExceptionHandler().handleEventException(ex, sequence, event); } /** * Delegate to {@link ExceptionHandler#handleOnStartException(Throwable)} on the delegate or * the default {@link ExceptionHandler} if one has not been configured. */ private void handleOnStartException(final Throwable ex) { getExceptionHandler().handleOnStartException(ex); } /** * Delegate to {@link ExceptionHandler#handleOnShutdownException(Throwable)} on the delegate or * the default {@link ExceptionHandler} if one has not been configured. */ private void handleOnShutdownException(final Throwable ex) { getExceptionHandler().handleOnShutdownException(ex); } private ExceptionHandler getExceptionHandler() { ExceptionHandler handler = exceptionHandler; return handler == null ? ExceptionHandlers.defaultHandler() : handler; } private class TryRewindHandler implements RewindHandler { private final BatchRewindStrategy batchRewindStrategy; TryRewindHandler(final BatchRewindStrategy batchRewindStrategy) { this.batchRewindStrategy = batchRewindStrategy; } @Override public long attemptRewindGetNextSequence(final RewindableException e, final long startOfBatchSequence) throws RewindableException { if (batchRewindStrategy.handleRewindException(e, ++retriesAttempted) == REWIND) { return startOfBatchSequence; } else { retriesAttempted = 0; throw e; } } } private static class NoRewindHandler implements RewindHandler { @Override public long attemptRewindGetNextSequence(final RewindableException e, final long startOfBatchSequence) { throw new UnsupportedOperationException("Rewindable Exception thrown from a non-rewindable event handler", e); } } } ================================================ FILE: src/main/java/com/lmax/disruptor/BatchEventProcessorBuilder.java ================================================ /* * Copyright 2023 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; public final class BatchEventProcessorBuilder { private int maxBatchSize = Integer.MAX_VALUE; /** * Set the maximum number of events that will be processed in a batch before updating the sequence. * * @param maxBatchSize max number of events to process in one batch. * @return The builder */ public BatchEventProcessorBuilder setMaxBatchSize(final int maxBatchSize) { this.maxBatchSize = maxBatchSize; return this; } /** * Construct a {@link EventProcessor} that will automatically track the progress by updating its sequence when * the {@link EventHandler#onEvent(Object, long, boolean)} method returns. * *

The created {@link BatchEventProcessor} will not support batch rewind, * but {@link EventHandler#setSequenceCallback(Sequence)} will be supported. * * @param dataProvider to which events are published. * @param sequenceBarrier on which it is waiting. * @param eventHandler is the delegate to which events are dispatched. * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. * @return the BatchEventProcessor */ public BatchEventProcessor build( final DataProvider dataProvider, final SequenceBarrier sequenceBarrier, final EventHandler eventHandler) { final BatchEventProcessor processor = new BatchEventProcessor<>( dataProvider, sequenceBarrier, eventHandler, maxBatchSize, null ); eventHandler.setSequenceCallback(processor.getSequence()); return processor; } /** * Construct a {@link EventProcessor} that will automatically track the progress by updating its sequence when * the {@link EventHandler#onEvent(Object, long, boolean)} method returns. * * @param dataProvider to which events are published. * @param sequenceBarrier on which it is waiting. * @param rewindableEventHandler is the delegate to which events are dispatched. * @param batchRewindStrategy a {@link BatchRewindStrategy} for customizing how to handle a {@link RewindableException}. * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. * @return the BatchEventProcessor */ public BatchEventProcessor build( final DataProvider dataProvider, final SequenceBarrier sequenceBarrier, final RewindableEventHandler rewindableEventHandler, final BatchRewindStrategy batchRewindStrategy) { if (null == batchRewindStrategy) { throw new NullPointerException("batchRewindStrategy cannot be null when building a BatchEventProcessor"); } return new BatchEventProcessor<>( dataProvider, sequenceBarrier, rewindableEventHandler, maxBatchSize, batchRewindStrategy ); } } ================================================ FILE: src/main/java/com/lmax/disruptor/BatchRewindStrategy.java ================================================ package com.lmax.disruptor; /** * Strategy for handling a rewindableException when processing an event. */ public interface BatchRewindStrategy { /** * When a {@link RewindableException} is thrown, this will be called. * * @param e the exception that propagated from the {@link EventHandler}. * @param attempts how many attempts there have been for the batch * @return the decision of whether to rewind the batch or throw the exception */ RewindAction handleRewindException(RewindableException e, int attempts); } ================================================ FILE: src/main/java/com/lmax/disruptor/BlockingWaitStrategy.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Blocking strategy that uses a lock and condition variable for {@link EventProcessor}s waiting on a barrier. * *

This strategy can be used when throughput and low-latency are not as important as CPU resource. */ public final class BlockingWaitStrategy implements WaitStrategy { private final Object mutex = new Object(); @Override public long waitFor(final long sequence, final Sequence cursorSequence, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException { long availableSequence; if (cursorSequence.get() < sequence) { synchronized (mutex) { while (cursorSequence.get() < sequence) { barrier.checkAlert(); mutex.wait(); } } } while ((availableSequence = dependentSequence.get()) < sequence) { barrier.checkAlert(); Thread.onSpinWait(); } return availableSequence; } @Override public void signalAllWhenBlocking() { synchronized (mutex) { mutex.notifyAll(); } } @Override public String toString() { return "BlockingWaitStrategy{" + "mutex=" + mutex + '}'; } } ================================================ FILE: src/main/java/com/lmax/disruptor/BusySpinWaitStrategy.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Busy Spin strategy that uses a busy spin loop for {@link com.lmax.disruptor.EventProcessor}s waiting on a barrier. * *

This strategy will use CPU resource to avoid syscalls which can introduce latency jitter. It is best * used when threads can be bound to specific CPU cores. */ public final class BusySpinWaitStrategy implements WaitStrategy { @Override public long waitFor( final long sequence, final Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException { long availableSequence; while ((availableSequence = dependentSequence.get()) < sequence) { barrier.checkAlert(); Thread.onSpinWait(); } return availableSequence; } @Override public void signalAllWhenBlocking() { } } ================================================ FILE: src/main/java/com/lmax/disruptor/Cursored.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Implementors of this interface must provide a single long value * that represents their current cursor value. Used during dynamic * add/remove of Sequences from a * {@link SequenceGroups#addSequences(Object, java.util.concurrent.atomic.AtomicReferenceFieldUpdater, Cursored, Sequence...)}. */ public interface Cursored { /** * Get the current cursor value. * * @return current cursor value */ long getCursor(); } ================================================ FILE: src/main/java/com/lmax/disruptor/DataProvider.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Typically used to decouple classes from {@link RingBuffer} to allow easier testing * * @param The type provided by the implementation */ public interface DataProvider { /** * @param sequence The sequence at which to find the data * @return the data item located at that sequence */ T get(long sequence); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventFactory.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Called by the {@link RingBuffer} to pre-populate all the events to fill the RingBuffer. * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. */ public interface EventFactory { /** * Implementations should instantiate an event object, with all memory already allocated where possible. * * @return T newly constructed event instance. */ T newInstance(); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventHandler.java ================================================ /* * Copyright 2022 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Callback interface to be implemented for processing events as they become available in the {@link RingBuffer} * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. * @see BatchEventProcessor#setExceptionHandler(ExceptionHandler) if you want to handle exceptions propagated out of the handler. */ public interface EventHandler extends EventHandlerBase { /** * Called when a publisher has published an event to the {@link RingBuffer}. The {@link BatchEventProcessor} will * read messages from the {@link RingBuffer} in batches, where a batch is all of the events available to be * processed without having to wait for any new event to arrive. This can be useful for event handlers that need * to do slower operations like I/O as they can group together the data from multiple events into a single * operation. Implementations should ensure that the operation is always performed when endOfBatch is true as * the time between that message and the next one is indeterminate. * * @param event published to the {@link RingBuffer} * @param sequence of the event being processed * @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer} * @throws Exception if the EventHandler would like the exception handled further up the chain. */ @Override void onEvent(T event, long sequence, boolean endOfBatch) throws Exception; /** * Used by the {@link BatchEventProcessor} to set a callback allowing the {@link EventHandler} to notify * when it has finished consuming an event if this happens after the {@link EventHandler#onEvent(Object, long, boolean)} call. * *

Typically this would be used when the handler is performing some sort of batching operation such as writing to an IO * device; after the operation has completed, the implementation should call {@link Sequence#set} to update the * sequence and allow other processes that are dependent on this handler to progress. * * @param sequenceCallback callback on which to notify the {@link BatchEventProcessor} that the sequence has progressed. */ default void setSequenceCallback(Sequence sequenceCallback) { } } ================================================ FILE: src/main/java/com/lmax/disruptor/EventHandlerBase.java ================================================ /* * Copyright 2022 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; @FunctionalInterface interface EventHandlerBase extends EventHandlerIdentity { /** * Called when a publisher has published an event to the {@link RingBuffer}. The {@link BatchEventProcessor} will * read messages from the {@link RingBuffer} in batches, where a batch is all of the events available to be * processed without having to wait for any new event to arrive. This can be useful for event handlers that need * to do slower operations like I/O as they can group together the data from multiple events into a single * operation. Implementations should ensure that the operation is always performed when endOfBatch is true as * the time between that message and the next one is indeterminate. * * @param event published to the {@link RingBuffer} * @param sequence of the event being processed * @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer} * @throws Throwable if the EventHandler would like the exception handled further up the chain or possible rewind * the batch if a {@link RewindableException} is thrown. */ void onEvent(T event, long sequence, boolean endOfBatch) throws Throwable; /** * Invoked by {@link BatchEventProcessor} prior to processing a batch of events * * @param batchSize the size of the batch that is starting * @param queueDepth the total number of queued up events including the batch about to be processed */ default void onBatchStart(long batchSize, long queueDepth) { } /** * Called once on thread start before first event is available. */ default void onStart() { } /** * Called once just before the event processing thread is shutdown. * *

Sequence event processing will already have stopped before this method is called. No events will * be processed after this message. */ default void onShutdown() { } /** * Invoked when a {@link BatchEventProcessor}'s {@link WaitStrategy} throws a {@link TimeoutException}. * * @param sequence - the last processed sequence. * @throws Exception if the implementation is unable to handle this timeout. */ default void onTimeout(long sequence) throws Exception { } } ================================================ FILE: src/main/java/com/lmax/disruptor/EventHandlerIdentity.java ================================================ package com.lmax.disruptor; public interface EventHandlerIdentity { } ================================================ FILE: src/main/java/com/lmax/disruptor/EventPoller.java ================================================ package com.lmax.disruptor; /** * Experimental poll-based interface for the Disruptor. Unlike a {@link BatchEventProcessor}, * an event poller allows the user to control the flow of execution. This makes it ideal * for interoperability with existing threads whose lifecycle is not controlled by the * disruptor DSL. * * @param the type of event used. */ public class EventPoller { private final DataProvider dataProvider; private final Sequencer sequencer; private final Sequence sequence; private final Sequence gatingSequence; /** * A callback used to process events * * @param the type of the event */ public interface Handler { /** * Called for each event to consume it * * @param event the event * @param sequence the sequence of the event * @param endOfBatch whether this event is the last in the batch * @return whether to continue consuming events. If {@code false}, the poller will not feed any more events * to the handler until {@link EventPoller#poll(Handler)} is called again * @throws Exception any exceptions thrown by the handler will be propagated to the caller of {@code poll} */ boolean onEvent(T event, long sequence, boolean endOfBatch) throws Exception; } /** * Indicates the result of a call to {@link #poll(Handler)} */ public enum PollState { /** * The poller processed one or more events */ PROCESSING, /** * The poller is waiting for gated sequences to advance before events become available */ GATING, /** * No events need to be processed */ IDLE } /** * Creates an event poller. Most users will want {@link RingBuffer#newPoller(Sequence...)} * which will set up the poller automatically * * @param dataProvider from which events are drawn * @param sequencer the main sequencer which handles ordering of events * @param sequence the sequence which will be used by this event poller * @param gatingSequence the sequences to gate on */ public EventPoller( final DataProvider dataProvider, final Sequencer sequencer, final Sequence sequence, final Sequence gatingSequence) { this.dataProvider = dataProvider; this.sequencer = sequencer; this.sequence = sequence; this.gatingSequence = gatingSequence; } /** * Polls for events using the given handler.
*
* This poller will continue to feed events to the given handler until known available * events are consumed or {@link Handler#onEvent(Object, long, boolean)} returns false.
*
* Note that it is possible for more events to become available while the current events * are being processed. A further call to this method will process such events. * * @param eventHandler the handler used to consume events * @return the state of the event poller after the poll is attempted * @throws Exception exceptions thrown from the event handler are propagated to the caller */ public PollState poll(final Handler eventHandler) throws Exception { final long currentSequence = sequence.get(); long nextSequence = currentSequence + 1; final long availableSequence = sequencer.getHighestPublishedSequence(nextSequence, gatingSequence.get()); if (nextSequence <= availableSequence) { boolean processNextEvent; long processedSequence = currentSequence; try { do { final T event = dataProvider.get(nextSequence); processNextEvent = eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence); processedSequence = nextSequence; nextSequence++; } while (nextSequence <= availableSequence && processNextEvent); } finally { sequence.set(processedSequence); } return PollState.PROCESSING; } else if (sequencer.getCursor() >= nextSequence) { return PollState.GATING; } else { return PollState.IDLE; } } /** * Creates an event poller. Most users will want {@link RingBuffer#newPoller(Sequence...)} * which will set up the poller automatically * * @param dataProvider from which events are drawn * @param sequencer the main sequencer which handles ordering of events * @param sequence the sequence which will be used by this event poller * @param cursorSequence the cursor sequence, usually of the ring buffer * @param gatingSequences additional sequences to gate on * @param the type of the event * @return the event poller */ public static EventPoller newInstance( final DataProvider dataProvider, final Sequencer sequencer, final Sequence sequence, final Sequence cursorSequence, final Sequence... gatingSequences) { Sequence gatingSequence; if (gatingSequences.length == 0) { gatingSequence = cursorSequence; } else if (gatingSequences.length == 1) { gatingSequence = gatingSequences[0]; } else { gatingSequence = new FixedSequenceGroup(gatingSequences); } return new EventPoller<>(dataProvider, sequencer, sequence, gatingSequence); } /** * Get the {@link Sequence} being used by this event poller * * @return the sequence used by the event poller */ public Sequence getSequence() { return sequence; } } ================================================ FILE: src/main/java/com/lmax/disruptor/EventProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * An EventProcessor needs to be an implementation of a runnable that will poll for events from the {@link RingBuffer} * using the appropriate wait strategy. It is unlikely that you will need to implement this interface yourself. * Look at using the {@link EventHandler} interface along with the pre-supplied BatchEventProcessor in the first * instance. * *

An EventProcessor will generally be associated with a Thread for execution. */ public interface EventProcessor extends Runnable { /** * Get a reference to the {@link Sequence} being used by this {@link EventProcessor}. * * @return reference to the {@link Sequence} for this {@link EventProcessor} */ Sequence getSequence(); /** * Signal that this EventProcessor should stop when it has finished consuming at the next clean break. * It will call {@link SequenceBarrier#alert()} to notify the thread to check status. */ void halt(); /** * @return whether this event processor is running or not * Implementations should ideally return false only when the associated thread is idle. */ boolean isRunning(); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventSequencer.java ================================================ package com.lmax.disruptor; /** * Pulls together the low-level data access and sequencing operations of {@link RingBuffer} * @param The event type */ public interface EventSequencer extends DataProvider, Sequenced { } ================================================ FILE: src/main/java/com/lmax/disruptor/EventSink.java ================================================ package com.lmax.disruptor; /** * Write interface for {@link RingBuffer}. * @param The event type */ public interface EventSink { /** * Publishes an event to the ring buffer. It handles * claiming the next sequence, getting the current (uninitialised) * event from the ring buffer and publishing the claimed sequence * after translation. * * @param translator The user specified translation for the event */ void publishEvent(EventTranslator translator); /** * Attempts to publish an event to the ring buffer. It handles * claiming the next sequence, getting the current (uninitialised) * event from the ring buffer and publishing the claimed sequence * after translation. Will return false if specified capacity * was not available. * * @param translator The user specified translation for the event * @return true if the value was published, false if there was insufficient * capacity. */ boolean tryPublishEvent(EventTranslator translator); /** * Allows one user supplied argument. * * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 A user supplied argument. * @see #publishEvent(EventTranslator) */ void publishEvent(EventTranslatorOneArg translator, A arg0); /** * Allows one user supplied argument. * * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 A user supplied argument. * @return true if the value was published, false if there was insufficient * capacity. * @see #tryPublishEvent(EventTranslator) */ boolean tryPublishEvent(EventTranslatorOneArg translator, A arg0); /** * Allows two user supplied arguments. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 A user supplied argument. * @param arg1 A user supplied argument. * @see #publishEvent(EventTranslator) */ void publishEvent(EventTranslatorTwoArg translator, A arg0, B arg1); /** * Allows two user supplied arguments. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 A user supplied argument. * @param arg1 A user supplied argument. * @return true if the value was published, false if there was insufficient * capacity. * @see #tryPublishEvent(EventTranslator) */ boolean tryPublishEvent(EventTranslatorTwoArg translator, A arg0, B arg1); /** * Allows three user supplied arguments * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 A user supplied argument. * @param arg1 A user supplied argument. * @param arg2 A user supplied argument. * @see #publishEvent(EventTranslator) */ void publishEvent(EventTranslatorThreeArg translator, A arg0, B arg1, C arg2); /** * Allows three user supplied arguments * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 A user supplied argument. * @param arg1 A user supplied argument. * @param arg2 A user supplied argument. * @return true if the value was published, false if there was insufficient * capacity. * @see #publishEvent(EventTranslator) */ boolean tryPublishEvent(EventTranslatorThreeArg translator, A arg0, B arg1, C arg2); /** * Allows a variable number of user supplied arguments * * @param translator The user specified translation for the event * @param args User supplied arguments. * @see #publishEvent(EventTranslator) */ void publishEvent(EventTranslatorVararg translator, Object... args); /** * Allows a variable number of user supplied arguments * * @param translator The user specified translation for the event * @param args User supplied arguments. * @return true if the value was published, false if there was insufficient * capacity. * @see #publishEvent(EventTranslator) */ boolean tryPublishEvent(EventTranslatorVararg translator, Object... args); /** *

Publishes multiple events to the ring buffer. It handles * claiming the next sequence, getting the current (uninitialised) * event from the ring buffer and publishing the claimed sequence * after translation.

* *

With this call the data that is to be inserted into the ring * buffer will be a field (either explicitly or captured anonymously), * therefore this call will require an instance of the translator * for each value that is to be inserted into the ring buffer.

* * @param translators The user specified translation for each event */ void publishEvents(EventTranslator[] translators); /** *

Publishes multiple events to the ring buffer. It handles * claiming the next sequence, getting the current (uninitialised) * event from the ring buffer and publishing the claimed sequence * after translation.

* *

With this call the data that is to be inserted into the ring * buffer will be a field (either explicitly or captured anonymously), * therefore this call will require an instance of the translator * for each value that is to be inserted into the ring buffer.

* * @param translators The user specified translation for each event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch */ void publishEvents(EventTranslator[] translators, int batchStartsAt, int batchSize); /** * Attempts to publish multiple events to the ring buffer. It handles * claiming the next sequence, getting the current (uninitialised) * event from the ring buffer and publishing the claimed sequence * after translation. Will return false if specified capacity * was not available. * * @param translators The user specified translation for the event * @return true if the value was published, false if there was insufficient * capacity. */ boolean tryPublishEvents(EventTranslator[] translators); /** * Attempts to publish multiple events to the ring buffer. It handles * claiming the next sequence, getting the current (uninitialised) * event from the ring buffer and publishing the claimed sequence * after translation. Will return false if specified capacity * was not available. * * @param translators The user specified translation for the event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch * @return true if all the values were published, false if there was insufficient * capacity. */ boolean tryPublishEvents(EventTranslator[] translators, int batchStartsAt, int batchSize); /** * Allows one user supplied argument per event. * * @param
Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 A user supplied argument. * @see #publishEvents(com.lmax.disruptor.EventTranslator[]) */ void publishEvents(EventTranslatorOneArg translator, A[] arg0); /** * Allows one user supplied argument per event. * * @param Class of the user supplied argument * @param translator The user specified translation for each event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch * @param arg0 An array of user supplied arguments, one element per event. * @see #publishEvents(EventTranslator[]) */ void publishEvents(EventTranslatorOneArg translator, int batchStartsAt, int batchSize, A[] arg0); /** * Allows one user supplied argument. * * @param Class of the user supplied argument * @param translator The user specified translation for each event * @param arg0 An array of user supplied arguments, one element per event. * @return true if the value was published, false if there was insufficient * capacity. * @see #tryPublishEvents(com.lmax.disruptor.EventTranslator[]) */ boolean tryPublishEvents(EventTranslatorOneArg translator, A[] arg0); /** * Allows one user supplied argument. * * @param Class of the user supplied argument * @param translator The user specified translation for each event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch * @param arg0 An array of user supplied arguments, one element per event. * @return true if the value was published, false if there was insufficient * capacity. * @see #tryPublishEvents(EventTranslator[]) */ boolean tryPublishEvents(EventTranslatorOneArg translator, int batchStartsAt, int batchSize, A[] arg0); /** * Allows two user supplied arguments per event. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 An array of user supplied arguments, one element per event. * @param arg1 An array of user supplied arguments, one element per event. * @see #publishEvents(com.lmax.disruptor.EventTranslator[]) */ void publishEvents(EventTranslatorTwoArg translator, A[] arg0, B[] arg1); /** * Allows two user supplied arguments per event. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch. * @param arg0 An array of user supplied arguments, one element per event. * @param arg1 An array of user supplied arguments, one element per event. * @see #publishEvents(EventTranslator[]) */ void publishEvents( EventTranslatorTwoArg translator, int batchStartsAt, int batchSize, A[] arg0, B[] arg1); /** * Allows two user supplied arguments per event. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 An array of user supplied arguments, one element per event. * @param arg1 An array of user supplied arguments, one element per event. * @return true if the value was published, false if there was insufficient * capacity. * @see #tryPublishEvents(com.lmax.disruptor.EventTranslator[]) */ boolean tryPublishEvents(EventTranslatorTwoArg translator, A[] arg0, B[] arg1); /** * Allows two user supplied arguments per event. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch. * @param arg0 An array of user supplied arguments, one element per event. * @param arg1 An array of user supplied arguments, one element per event. * @return true if the value was published, false if there was insufficient * capacity. * @see #tryPublishEvents(EventTranslator[]) */ boolean tryPublishEvents( EventTranslatorTwoArg translator, int batchStartsAt, int batchSize, A[] arg0, B[] arg1); /** * Allows three user supplied arguments per event. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 An array of user supplied arguments, one element per event. * @param arg1 An array of user supplied arguments, one element per event. * @param arg2 An array of user supplied arguments, one element per event. * @see #publishEvents(com.lmax.disruptor.EventTranslator[]) */ void publishEvents(EventTranslatorThreeArg translator, A[] arg0, B[] arg1, C[] arg2); /** * Allows three user supplied arguments per event. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The number of elements in the batch. * @param arg0 An array of user supplied arguments, one element per event. * @param arg1 An array of user supplied arguments, one element per event. * @param arg2 An array of user supplied arguments, one element per event. * @see #publishEvents(EventTranslator[]) */ void publishEvents( EventTranslatorThreeArg translator, int batchStartsAt, int batchSize, A[] arg0, B[] arg1, C[] arg2); /** * Allows three user supplied arguments per event. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param arg0 An array of user supplied arguments, one element per event. * @param arg1 An array of user supplied arguments, one element per event. * @param arg2 An array of user supplied arguments, one element per event. * @return true if the value was published, false if there was insufficient * capacity. * @see #publishEvents(com.lmax.disruptor.EventTranslator[]) */ boolean tryPublishEvents(EventTranslatorThreeArg translator, A[] arg0, B[] arg1, C[] arg2); /** * Allows three user supplied arguments per event. * * @param Class of the user supplied argument * @param Class of the user supplied argument * @param Class of the user supplied argument * @param translator The user specified translation for the event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch. * @param arg0 An array of user supplied arguments, one element per event. * @param arg1 An array of user supplied arguments, one element per event. * @param arg2 An array of user supplied arguments, one element per event. * @return true if the value was published, false if there was insufficient * capacity. * @see #publishEvents(EventTranslator[]) */ boolean tryPublishEvents( EventTranslatorThreeArg translator, int batchStartsAt, int batchSize, A[] arg0, B[] arg1, C[] arg2); /** * Allows a variable number of user supplied arguments per event. * * @param translator The user specified translation for the event * @param args User supplied arguments, one Object[] per event. * @see #publishEvents(com.lmax.disruptor.EventTranslator[]) */ void publishEvents(EventTranslatorVararg translator, Object[]... args); /** * Allows a variable number of user supplied arguments per event. * * @param translator The user specified translation for the event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch * @param args User supplied arguments, one Object[] per event. * @see #publishEvents(EventTranslator[]) */ void publishEvents(EventTranslatorVararg translator, int batchStartsAt, int batchSize, Object[]... args); /** * Allows a variable number of user supplied arguments per event. * * @param translator The user specified translation for the event * @param args User supplied arguments, one Object[] per event. * @return true if the value was published, false if there was insufficient * capacity. * @see #publishEvents(com.lmax.disruptor.EventTranslator[]) */ boolean tryPublishEvents(EventTranslatorVararg translator, Object[]... args); /** * Allows a variable number of user supplied arguments per event. * * @param translator The user specified translation for the event * @param batchStartsAt The first element of the array which is within the batch. * @param batchSize The actual size of the batch. * @param args User supplied arguments, one Object[] per event. * @return true if the value was published, false if there was insufficient * capacity. * @see #publishEvents(EventTranslator[]) */ boolean tryPublishEvents(EventTranslatorVararg translator, int batchStartsAt, int batchSize, Object[]... args); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventTranslator.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Implementations translate (write) data representations into events claimed from the {@link RingBuffer}. * *

Strategy for handling a rewindableException that will eventually delegate the exception to the * {@link ExceptionHandler} after a specified number of attempts have been made.

*/ public class EventuallyGiveUpBatchRewindStrategy implements BatchRewindStrategy { private final long maxAttempts; /** * @param maxAttempts numbers of Rewindable exceptions that can be thrown until exception is delegated */ public EventuallyGiveUpBatchRewindStrategy(final long maxAttempts) { this.maxAttempts = maxAttempts; } @Override public RewindAction handleRewindException(final RewindableException e, final int retriesAttempted) { if (retriesAttempted == maxAttempts) { return RewindAction.THROW; } return RewindAction.REWIND; } } ================================================ FILE: src/main/java/com/lmax/disruptor/ExceptionHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Callback handler for uncaught exceptions in the event processing cycle of the {@link BatchEventProcessor} * * @param implementation storing the data for sharing during exchange or parallel coordination of an event. */ public interface ExceptionHandler { /** *

Strategy for handling uncaught exceptions when processing an event.

* *

If the strategy wishes to terminate further processing by the {@link BatchEventProcessor} * then it should throw a {@link RuntimeException}.

* * @param ex the exception that propagated from the {@link EventHandler}. * @param sequence of the event which cause the exception. * @param event being processed when the exception occurred. This can be null. */ void handleEventException(Throwable ex, long sequence, T event); /** * Callback to notify of an exception during {@link EventHandler#onStart()} * * @param ex throw during the starting process. */ void handleOnStartException(Throwable ex); /** * Callback to notify of an exception during {@link EventHandler#onShutdown()} * * @param ex throw during the shutdown process. */ void handleOnShutdownException(Throwable ex); } ================================================ FILE: src/main/java/com/lmax/disruptor/ExceptionHandlers.java ================================================ /* * Copyright 2021 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** Provides static methods for accessing a default {@link ExceptionHandler} object. */ public final class ExceptionHandlers { /** * Get a reference to the default {@link ExceptionHandler} instance. * * @return a reference to the default {@link ExceptionHandler} instance */ public static ExceptionHandler defaultHandler() { return DefaultExceptionHandlerHolder.HANDLER; } private ExceptionHandlers() { } // lazily initialize the default exception handler. // This nested object isn't strictly necessary unless additional utility functionality is // added to ExceptionHandlers, but it exists to ensure the code remains obvious. private static final class DefaultExceptionHandlerHolder { private static final ExceptionHandler HANDLER = new FatalExceptionHandler(); } } ================================================ FILE: src/main/java/com/lmax/disruptor/FatalExceptionHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import java.lang.System.Logger; import java.lang.System.Logger.Level; /** * Convenience implementation of an exception handler that uses the standard JDK logging * of {@link System.Logger} to log the exception as {@link Level}.ERROR and re-throw * it wrapped in a {@link RuntimeException} */ public final class FatalExceptionHandler implements ExceptionHandler { private static final Logger LOGGER = System.getLogger(FatalExceptionHandler.class.getName()); @Override public void handleEventException(final Throwable ex, final long sequence, final Object event) { LOGGER.log(Level.ERROR, () -> "Exception processing: " + sequence + " " + event, ex); throw new RuntimeException(ex); } @Override public void handleOnStartException(final Throwable ex) { LOGGER.log(Level.ERROR, "Exception during onStart()", ex); } @Override public void handleOnShutdownException(final Throwable ex) { LOGGER.log(Level.ERROR, "Exception during onShutdown()", ex); } } ================================================ FILE: src/main/java/com/lmax/disruptor/FixedSequenceGroup.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.util.Util; import java.util.Arrays; /** * Hides a group of Sequences behind a single Sequence */ public final class FixedSequenceGroup extends Sequence { private final Sequence[] sequences; /** * Constructor * * @param sequences the list of sequences to be tracked under this sequence group */ public FixedSequenceGroup(final Sequence[] sequences) { this.sequences = Arrays.copyOf(sequences, sequences.length); } /** * Get the minimum sequence value for the group. * * @return the minimum sequence value for the group. */ @Override public long get() { return Util.getMinimumSequence(sequences); } @Override public String toString() { return Arrays.toString(sequences); } /** * Not supported. */ @Override public void set(final long value) { throw new UnsupportedOperationException(); } /** * Not supported. */ @Override public boolean compareAndSet(final long expectedValue, final long newValue) { throw new UnsupportedOperationException(); } /** * Not supported. */ @Override public long incrementAndGet() { throw new UnsupportedOperationException(); } /** * Not supported. */ @Override public long addAndGet(final long increment) { throw new UnsupportedOperationException(); } } ================================================ FILE: src/main/java/com/lmax/disruptor/IgnoreExceptionHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import java.lang.System.Logger; import java.lang.System.Logger.Level; /** * Convenience implementation of an exception handler that uses the standard JDK logging * of {@link System.Logger} to log the exception as {@link Level}.INFO */ public final class IgnoreExceptionHandler implements ExceptionHandler { private static final Logger LOGGER = System.getLogger(IgnoreExceptionHandler.class.getName()); @Override public void handleEventException(final Throwable ex, final long sequence, final Object event) { LOGGER.log(Level.INFO, () -> "Exception processing: " + sequence + " " + event, ex); } @Override public void handleOnStartException(final Throwable ex) { LOGGER.log(Level.INFO, "Exception during onStart()", ex); } @Override public void handleOnShutdownException(final Throwable ex) { LOGGER.log(Level.INFO, "Exception during onShutdown()", ex); } } ================================================ FILE: src/main/java/com/lmax/disruptor/InsufficientCapacityException.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Exception thrown when it is not possible to insert a value into * the ring buffer without it wrapping the consuming sequences. Used * specifically when claiming with the {@link RingBuffer#tryNext()} call. * *

For efficiency this exception will not have a stack trace. */ @SuppressWarnings({"serial", "lgtm[java/non-sync-override]"}) public final class InsufficientCapacityException extends Exception { /** * The efficiency saving singleton instance */ public static final InsufficientCapacityException INSTANCE = new InsufficientCapacityException(); private InsufficientCapacityException() { // Singleton } @Override public Throwable fillInStackTrace() { return this; } } ================================================ FILE: src/main/java/com/lmax/disruptor/LiteBlockingWaitStrategy.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import java.util.concurrent.atomic.AtomicBoolean; /** * Variation of the {@link BlockingWaitStrategy} that attempts to elide conditional wake-ups when * the lock is uncontended. Shows performance improvements on microbenchmarks. However this * wait strategy should be considered experimental as I have not full proved the correctness of * the lock elision code. */ public final class LiteBlockingWaitStrategy implements WaitStrategy { private final Object mutex = new Object(); private final AtomicBoolean signalNeeded = new AtomicBoolean(false); @Override public long waitFor(final long sequence, final Sequence cursorSequence, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException { long availableSequence; if (cursorSequence.get() < sequence) { synchronized (mutex) { do { signalNeeded.getAndSet(true); if (cursorSequence.get() >= sequence) { break; } barrier.checkAlert(); mutex.wait(); } while (cursorSequence.get() < sequence); } } while ((availableSequence = dependentSequence.get()) < sequence) { barrier.checkAlert(); Thread.onSpinWait(); } return availableSequence; } @Override public void signalAllWhenBlocking() { if (signalNeeded.getAndSet(false)) { synchronized (mutex) { mutex.notifyAll(); } } } @Override public String toString() { return "LiteBlockingWaitStrategy{" + "mutex=" + mutex + ", signalNeeded=" + signalNeeded + '}'; } } ================================================ FILE: src/main/java/com/lmax/disruptor/LiteTimeoutBlockingWaitStrategy.java ================================================ package com.lmax.disruptor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static com.lmax.disruptor.util.Util.awaitNanos; /** * Variation of the {@link TimeoutBlockingWaitStrategy} that attempts to elide conditional wake-ups * when the lock is uncontended. */ public class LiteTimeoutBlockingWaitStrategy implements WaitStrategy { private final Object mutex = new Object(); private final AtomicBoolean signalNeeded = new AtomicBoolean(false); private final long timeoutInNanos; /** * @param timeout how long to wait before timing out * @param units the unit in which timeout is specified */ public LiteTimeoutBlockingWaitStrategy(final long timeout, final TimeUnit units) { timeoutInNanos = units.toNanos(timeout); } @Override public long waitFor( final long sequence, final Sequence cursorSequence, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException, TimeoutException { long nanos = timeoutInNanos; long availableSequence; if (cursorSequence.get() < sequence) { synchronized (mutex) { while (cursorSequence.get() < sequence) { signalNeeded.getAndSet(true); barrier.checkAlert(); nanos = awaitNanos(mutex, nanos); if (nanos <= 0) { throw TimeoutException.INSTANCE; } } } } while ((availableSequence = dependentSequence.get()) < sequence) { barrier.checkAlert(); } return availableSequence; } @Override public void signalAllWhenBlocking() { if (signalNeeded.getAndSet(false)) { synchronized (mutex) { mutex.notifyAll(); } } } @Override public String toString() { return "LiteTimeoutBlockingWaitStrategy{" + "mutex=" + mutex + ", signalNeeded=" + signalNeeded + ", timeoutInNanos=" + timeoutInNanos + '}'; } } ================================================ FILE: src/main/java/com/lmax/disruptor/MultiProducerSequencer.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.util.Util; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.Arrays; import java.util.concurrent.locks.LockSupport; /** * Coordinator for claiming sequences for access to a data structure while tracking dependent {@link Sequence}s. * Suitable for use for sequencing across multiple publisher threads. * *

Note on {@link Sequencer#getCursor()}: With this sequencer the cursor value is updated after the call * to {@link Sequencer#next()}, to determine the highest available sequence that can be read, then * {@link Sequencer#getHighestPublishedSequence(long, long)} should be used. */ public final class MultiProducerSequencer extends AbstractSequencer { private static final VarHandle AVAILABLE_ARRAY = MethodHandles.arrayElementVarHandle(int[].class); private final Sequence gatingSequenceCache = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); // availableBuffer tracks the state of each ringbuffer slot // see below for more details on the approach private final int[] availableBuffer; private final int indexMask; private final int indexShift; /** * Construct a Sequencer with the selected wait strategy and buffer size. * * @param bufferSize the size of the buffer that this will sequence over. * @param waitStrategy for those waiting on sequences. */ public MultiProducerSequencer(final int bufferSize, final WaitStrategy waitStrategy) { super(bufferSize, waitStrategy); availableBuffer = new int[bufferSize]; Arrays.fill(availableBuffer, -1); indexMask = bufferSize - 1; indexShift = Util.log2(bufferSize); } /** * @see Sequencer#hasAvailableCapacity(int) */ @Override public boolean hasAvailableCapacity(final int requiredCapacity) { return hasAvailableCapacity(gatingSequences, requiredCapacity, cursor.get()); } private boolean hasAvailableCapacity(final Sequence[] gatingSequences, final int requiredCapacity, final long cursorValue) { long wrapPoint = (cursorValue + requiredCapacity) - bufferSize; long cachedGatingSequence = gatingSequenceCache.get(); if (wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue) { long minSequence = Util.getMinimumSequence(gatingSequences, cursorValue); gatingSequenceCache.set(minSequence); if (wrapPoint > minSequence) { return false; } } return true; } /** * @see Sequencer#claim(long) */ @Override public void claim(final long sequence) { cursor.set(sequence); } /** * @see Sequencer#next() */ @Override public long next() { return next(1); } /** * @see Sequencer#next(int) */ @Override public long next(final int n) { if (n < 1 || n > bufferSize) { throw new IllegalArgumentException("n must be > 0 and < bufferSize"); } long current = cursor.getAndAdd(n); long nextSequence = current + n; long wrapPoint = nextSequence - bufferSize; long cachedGatingSequence = gatingSequenceCache.get(); if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current) { long gatingSequence; while (wrapPoint > (gatingSequence = Util.getMinimumSequence(gatingSequences, current))) { LockSupport.parkNanos(1L); // TODO, should we spin based on the wait strategy? } gatingSequenceCache.set(gatingSequence); } return nextSequence; } /** * @see Sequencer#tryNext() */ @Override public long tryNext() throws InsufficientCapacityException { return tryNext(1); } /** * @see Sequencer#tryNext(int) */ @Override public long tryNext(final int n) throws InsufficientCapacityException { if (n < 1) { throw new IllegalArgumentException("n must be > 0"); } long current; long next; do { current = cursor.get(); next = current + n; if (!hasAvailableCapacity(gatingSequences, n, current)) { throw InsufficientCapacityException.INSTANCE; } } while (!cursor.compareAndSet(current, next)); return next; } /** * @see Sequencer#remainingCapacity() */ @Override public long remainingCapacity() { long consumed = Util.getMinimumSequence(gatingSequences, cursor.get()); long produced = cursor.get(); return getBufferSize() - (produced - consumed); } /** * @see Sequencer#publish(long) */ @Override public void publish(final long sequence) { setAvailable(sequence); waitStrategy.signalAllWhenBlocking(); } /** * @see Sequencer#publish(long, long) */ @Override public void publish(final long lo, final long hi) { for (long l = lo; l <= hi; l++) { setAvailable(l); } waitStrategy.signalAllWhenBlocking(); } /** * The below methods work on the availableBuffer flag. * *

The prime reason is to avoid a shared sequence object between publisher threads. * (Keeping single pointers tracking start and end would require coordination * between the threads). * *

-- Firstly we have the constraint that the delta between the cursor and minimum * gating sequence will never be larger than the buffer size (the code in * next/tryNext in the Sequence takes care of that). * -- Given that; take the sequence value and mask off the lower portion of the * sequence as the index into the buffer (indexMask). (aka modulo operator) * -- The upper portion of the sequence becomes the value to check for availability. * ie: it tells us how many times around the ring buffer we've been (aka division) * -- Because we can't wrap without the gating sequences moving forward (i.e. the * minimum gating sequence is effectively our last available position in the * buffer), when we have new data and successfully claimed a slot we can simply * write over the top. */ private void setAvailable(final long sequence) { setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence)); } private void setAvailableBufferValue(final int index, final int flag) { AVAILABLE_ARRAY.setRelease(availableBuffer, index, flag); } /** * @see Sequencer#isAvailable(long) */ @Override public boolean isAvailable(final long sequence) { int index = calculateIndex(sequence); int flag = calculateAvailabilityFlag(sequence); return (int) AVAILABLE_ARRAY.getAcquire(availableBuffer, index) == flag; } @Override public long getHighestPublishedSequence(final long lowerBound, final long availableSequence) { for (long sequence = lowerBound; sequence <= availableSequence; sequence++) { if (!isAvailable(sequence)) { return sequence - 1; } } return availableSequence; } private int calculateAvailabilityFlag(final long sequence) { return (int) (sequence >>> indexShift); } private int calculateIndex(final long sequence) { return ((int) sequence) & indexMask; } @Override public String toString() { return "MultiProducerSequencer{" + "bufferSize=" + bufferSize + ", waitStrategy=" + waitStrategy + ", cursor=" + cursor + ", gatingSequences=" + Arrays.toString(gatingSequences) + '}'; } } ================================================ FILE: src/main/java/com/lmax/disruptor/NanosecondPauseBatchRewindStrategy.java ================================================ package com.lmax.disruptor; import java.util.concurrent.locks.LockSupport; /** *

Strategy for handling a rewindableException that will pause for a specified amount of nanos.

*/ public class NanosecondPauseBatchRewindStrategy implements BatchRewindStrategy { private final long nanoSecondPauseTime; /** *

Strategy for handling a rewindableException that will pause for a specified amount of nanos.

* @param nanoSecondPauseTime Amount of nanos to pause for when a rewindable exception is thrown */ public NanosecondPauseBatchRewindStrategy(final long nanoSecondPauseTime) { this.nanoSecondPauseTime = nanoSecondPauseTime; } @Override public RewindAction handleRewindException(final RewindableException e, final int retriesAttempted) { LockSupport.parkNanos(nanoSecondPauseTime); return RewindAction.REWIND; } } ================================================ FILE: src/main/java/com/lmax/disruptor/NoOpEventProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import java.util.concurrent.atomic.AtomicBoolean; /** * No operation version of a {@link EventProcessor} that simply tracks a {@link Sequence}. * *

This is useful in tests or for pre-filling a {@link RingBuffer} from a publisher. */ public final class NoOpEventProcessor implements EventProcessor { private final SequencerFollowingSequence sequence; private final AtomicBoolean running = new AtomicBoolean(false); /** * Construct a {@link EventProcessor} that simply tracks a {@link Sequence} object. * * @param sequencer to track. */ public NoOpEventProcessor(final RingBuffer sequencer) { sequence = new SequencerFollowingSequence(sequencer); } @Override public Sequence getSequence() { return sequence; } @Override public void halt() { running.set(false); } @Override public boolean isRunning() { return running.get(); } @Override public void run() { if (!running.compareAndSet(false, true)) { throw new IllegalStateException("Thread is already running"); } } /** * Sequence that follows (by wrapping) another sequence */ private static final class SequencerFollowingSequence extends Sequence { private final RingBuffer sequencer; private SequencerFollowingSequence(final RingBuffer sequencer) { super(Sequencer.INITIAL_CURSOR_VALUE); this.sequencer = sequencer; } @Override public long get() { return sequencer.getCursor(); } } } ================================================ FILE: src/main/java/com/lmax/disruptor/PhasedBackoffWaitStrategy.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import java.util.concurrent.TimeUnit; /** * Phased wait strategy for waiting {@link EventProcessor}s on a barrier. * *

This strategy can be used when throughput and low-latency are not as important as CPU resource. * Spins, then yields, then waits using the configured fallback WaitStrategy. */ public final class PhasedBackoffWaitStrategy implements WaitStrategy { private static final int SPIN_TRIES = 10000; private final long spinTimeoutNanos; private final long yieldTimeoutNanos; private final WaitStrategy fallbackStrategy; /** * * @param spinTimeout The maximum time in to busy spin for. * @param yieldTimeout The maximum time in to yield for. * @param units Time units used for the timeout values. * @param fallbackStrategy After spinning + yielding, the strategy to fall back to */ public PhasedBackoffWaitStrategy( final long spinTimeout, final long yieldTimeout, final TimeUnit units, final WaitStrategy fallbackStrategy) { this.spinTimeoutNanos = units.toNanos(spinTimeout); this.yieldTimeoutNanos = spinTimeoutNanos + units.toNanos(yieldTimeout); this.fallbackStrategy = fallbackStrategy; } /** * Construct {@link PhasedBackoffWaitStrategy} with fallback to {@link BlockingWaitStrategy} * * @param spinTimeout The maximum time in to busy spin for. * @param yieldTimeout The maximum time in to yield for. * @param units Time units used for the timeout values. * @return The constructed wait strategy. */ public static PhasedBackoffWaitStrategy withLock( final long spinTimeout, final long yieldTimeout, final TimeUnit units) { return new PhasedBackoffWaitStrategy( spinTimeout, yieldTimeout, units, new BlockingWaitStrategy()); } /** * Construct {@link PhasedBackoffWaitStrategy} with fallback to {@link LiteBlockingWaitStrategy} * * @param spinTimeout The maximum time in to busy spin for. * @param yieldTimeout The maximum time in to yield for. * @param units Time units used for the timeout values. * @return The constructed wait strategy. */ public static PhasedBackoffWaitStrategy withLiteLock( final long spinTimeout, final long yieldTimeout, final TimeUnit units) { return new PhasedBackoffWaitStrategy( spinTimeout, yieldTimeout, units, new LiteBlockingWaitStrategy()); } /** * Construct {@link PhasedBackoffWaitStrategy} with fallback to {@link SleepingWaitStrategy} * * @param spinTimeout The maximum time in to busy spin for. * @param yieldTimeout The maximum time in to yield for. * @param units Time units used for the timeout values. * @return The constructed wait strategy. */ public static PhasedBackoffWaitStrategy withSleep( final long spinTimeout, final long yieldTimeout, final TimeUnit units) { return new PhasedBackoffWaitStrategy( spinTimeout, yieldTimeout, units, new SleepingWaitStrategy(0)); } @Override public long waitFor(final long sequence, final Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException, TimeoutException { long availableSequence; long startTime = 0; int counter = SPIN_TRIES; do { if ((availableSequence = dependentSequence.get()) >= sequence) { return availableSequence; } if (0 == --counter) { if (0 == startTime) { startTime = System.nanoTime(); } else { long timeDelta = System.nanoTime() - startTime; if (timeDelta > yieldTimeoutNanos) { return fallbackStrategy.waitFor(sequence, cursor, dependentSequence, barrier); } else if (timeDelta > spinTimeoutNanos) { Thread.yield(); } } counter = SPIN_TRIES; } } while (true); } @Override public void signalAllWhenBlocking() { fallbackStrategy.signalAllWhenBlocking(); } } ================================================ FILE: src/main/java/com/lmax/disruptor/ProcessingSequenceBarrier.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * {@link SequenceBarrier} handed out for gating {@link EventProcessor}s on a cursor sequence and optional dependent {@link EventProcessor}(s), * using the given WaitStrategy. */ final class ProcessingSequenceBarrier implements SequenceBarrier { private final WaitStrategy waitStrategy; private final Sequence dependentSequence; private volatile boolean alerted = false; private final Sequence cursorSequence; private final Sequencer sequencer; ProcessingSequenceBarrier( final Sequencer sequencer, final WaitStrategy waitStrategy, final Sequence cursorSequence, final Sequence[] dependentSequences) { this.sequencer = sequencer; this.waitStrategy = waitStrategy; this.cursorSequence = cursorSequence; if (0 == dependentSequences.length) { dependentSequence = cursorSequence; } else { dependentSequence = new FixedSequenceGroup(dependentSequences); } } @Override public long waitFor(final long sequence) throws AlertException, InterruptedException, TimeoutException { checkAlert(); long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this); if (availableSequence < sequence) { return availableSequence; } return sequencer.getHighestPublishedSequence(sequence, availableSequence); } @Override public long getCursor() { return dependentSequence.get(); } @Override public boolean isAlerted() { return alerted; } @Override public void alert() { alerted = true; waitStrategy.signalAllWhenBlocking(); } @Override public void clearAlert() { alerted = false; } @Override public void checkAlert() throws AlertException { if (alerted) { throw AlertException.INSTANCE; } } } ================================================ FILE: src/main/java/com/lmax/disruptor/RewindAction.java ================================================ package com.lmax.disruptor; /** * The result returned from the {@link BatchRewindStrategy} that decides whether to rewind or throw the exception */ public enum RewindAction { /** * Rewind and replay the whole batch from he beginning */ REWIND, /** * rethrows the exception, delegating it to the configured {@link ExceptionHandler} */ THROW } ================================================ FILE: src/main/java/com/lmax/disruptor/RewindHandler.java ================================================ /* * Copyright 2023 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; public interface RewindHandler { long attemptRewindGetNextSequence(RewindableException e, long startOfBatchSequence) throws RewindableException; } ================================================ FILE: src/main/java/com/lmax/disruptor/RewindableEventHandler.java ================================================ /* * Copyright 2022 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Callback interface to be implemented for processing events as they become available in the {@link RingBuffer} * with support for throwing a {@link RewindableException} when an even cannot be processed currently but may succeed on retry. * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. * @see BatchEventProcessor#setExceptionHandler(ExceptionHandler) if you want to handle exceptions propagated out of the handler. */ public interface RewindableEventHandler extends EventHandlerBase { /** * Called when a publisher has published an event to the {@link RingBuffer}. The {@link BatchEventProcessor} will * read messages from the {@link RingBuffer} in batches, where a batch is all of the events available to be * processed without having to wait for any new event to arrive. This can be useful for event handlers that need * to do slower operations like I/O as they can group together the data from multiple events into a single * operation. Implementations should ensure that the operation is always performed when endOfBatch is true as * the time between that message and the next one is indeterminate. * * @param event published to the {@link RingBuffer} * @param sequence of the event being processed * @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer} * @throws RewindableException if the EventHandler would like the batch event processor to process the entire batch again. * @throws Exception if the EventHandler would like the exception handled further up the chain. */ @Override void onEvent(T event, long sequence, boolean endOfBatch) throws RewindableException, Exception; } ================================================ FILE: src/main/java/com/lmax/disruptor/RewindableException.java ================================================ package com.lmax.disruptor; /** * A special exception that can be thrown while using the {@link BatchEventProcessor}. * On throwing this exception the {@link BatchEventProcessor} can choose to rewind and replay the batch or throw * depending on the {@link BatchRewindStrategy} */ public class RewindableException extends Throwable { /** * @param cause The underlying cause of the exception. */ public RewindableException(final Throwable cause) { super("REWINDING BATCH", cause); } } ================================================ FILE: src/main/java/com/lmax/disruptor/RingBuffer.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.dsl.ProducerType; abstract class RingBufferPad { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; } abstract class RingBufferFields extends RingBufferPad { private static final int BUFFER_PAD = 32; private final long indexMask; private final E[] entries; protected final int bufferSize; protected final Sequencer sequencer; @SuppressWarnings("unchecked") RingBufferFields( final EventFactory eventFactory, final Sequencer sequencer) { this.sequencer = sequencer; this.bufferSize = sequencer.getBufferSize(); if (bufferSize < 1) { throw new IllegalArgumentException("bufferSize must not be less than 1"); } if (Integer.bitCount(bufferSize) != 1) { throw new IllegalArgumentException("bufferSize must be a power of 2"); } this.indexMask = bufferSize - 1; this.entries = (E[]) new Object[bufferSize + 2 * BUFFER_PAD]; fill(eventFactory); } private void fill(final EventFactory eventFactory) { for (int i = 0; i < bufferSize; i++) { entries[BUFFER_PAD + i] = eventFactory.newInstance(); } } protected final E elementAt(final long sequence) { return entries[BUFFER_PAD + (int) (sequence & indexMask)]; } } /** * Ring based store of reusable entries containing the data representing * an event being exchanged between event producer and {@link EventProcessor}s. * * @param implementation storing the data for sharing during exchange or parallel coordination of an event. */ public final class RingBuffer extends RingBufferFields implements Cursored, EventSequencer, EventSink { /** * The initial cursor value */ public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE; protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; /** * Construct a RingBuffer with the full option set. * * @param eventFactory to newInstance entries for filling the RingBuffer * @param sequencer sequencer to handle the ordering of events moving through the RingBuffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 */ RingBuffer( final EventFactory eventFactory, final Sequencer sequencer) { super(eventFactory, sequencer); } /** * Create a new multiple producer RingBuffer with the specified wait strategy. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBuffer createMultiProducer( final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { MultiProducerSequencer sequencer = new MultiProducerSequencer(bufferSize, waitStrategy); return new RingBuffer<>(factory, sequencer); } /** * Create a new multiple producer RingBuffer using the default wait strategy {@link BlockingWaitStrategy}. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBuffer createMultiProducer(final EventFactory factory, final int bufferSize) { return createMultiProducer(factory, bufferSize, new BlockingWaitStrategy()); } /** * Create a new single producer RingBuffer with the specified wait strategy. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see SingleProducerSequencer */ public static RingBuffer createSingleProducer( final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy); return new RingBuffer<>(factory, sequencer); } /** * Create a new single producer RingBuffer using the default wait strategy {@link BlockingWaitStrategy}. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBuffer createSingleProducer(final EventFactory factory, final int bufferSize) { return createSingleProducer(factory, bufferSize, new BlockingWaitStrategy()); } /** * Create a new Ring Buffer with the specified producer type (SINGLE or MULTI) * * @param Class of the event stored in the ring buffer. * @param producerType producer type to use {@link ProducerType}. * @param factory used to create events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 */ public static RingBuffer create( final ProducerType producerType, final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { switch (producerType) { case SINGLE: return createSingleProducer(factory, bufferSize, waitStrategy); case MULTI: return createMultiProducer(factory, bufferSize, waitStrategy); default: throw new IllegalStateException(producerType.toString()); } } /** *

Get the event for a given sequence in the RingBuffer.

* *

This call has 2 uses. Firstly use this call when publishing to a ring buffer. * After calling {@link RingBuffer#next()} use this call to get hold of the * preallocated event to fill with data before calling {@link RingBuffer#publish(long)}.

* *

Secondly use this call when consuming data from the ring buffer. After calling * {@link SequenceBarrier#waitFor(long)} call this method with any value greater than * that your current consumer sequence and less than or equal to the value returned from * the {@link SequenceBarrier#waitFor(long)} method.

* * @param sequence for the event * @return the event for the given sequence */ @Override public E get(final long sequence) { return elementAt(sequence); } /** * Increment and return the next sequence for the ring buffer. Calls of this * method should ensure that they always publish the sequence afterward. E.g. *
     * long sequence = ringBuffer.next();
     * try {
     *     Event e = ringBuffer.get(sequence);
     *     // Do some work with the event.
     * } finally {
     *     ringBuffer.publish(sequence);
     * }
     * 
* * @return The next sequence to publish to. * @see RingBuffer#publish(long) * @see RingBuffer#get(long) */ @Override public long next() { return sequencer.next(); } /** * The same functionality as {@link RingBuffer#next()}, but allows the caller to claim * the next n sequences. * * @param n number of slots to claim * @return sequence number of the highest slot claimed * @see Sequencer#next(int) */ @Override public long next(final int n) { return sequencer.next(n); } /** *

Increment and return the next sequence for the ring buffer. Calls of this * method should ensure that they always publish the sequence afterward. E.g.

*
     * long sequence = ringBuffer.next();
     * try {
     *     Event e = ringBuffer.get(sequence);
     *     // Do some work with the event.
     * } finally {
     *     ringBuffer.publish(sequence);
     * }
     * 
*

This method will not block if there is not space available in the ring * buffer, instead it will throw an {@link InsufficientCapacityException}.

* * @return The next sequence to publish to. * @throws InsufficientCapacityException if the necessary space in the ring buffer is not available * @see RingBuffer#publish(long) * @see RingBuffer#get(long) */ @Override public long tryNext() throws InsufficientCapacityException { return sequencer.tryNext(); } /** * The same functionality as {@link RingBuffer#tryNext()}, but allows the caller to attempt * to claim the next n sequences. * * @param n number of slots to claim * @return sequence number of the highest slot claimed * @throws InsufficientCapacityException if the necessary space in the ring buffer is not available */ @Override public long tryNext(final int n) throws InsufficientCapacityException { return sequencer.tryNext(n); } /** * Sets the cursor to a specific sequence and returns the preallocated entry that is stored there. This * can cause a data race and should only be done in controlled circumstances, e.g. during initialisation. * * @param sequence The sequence to claim. * @return The preallocated event. */ public E claimAndGetPreallocated(final long sequence) { sequencer.claim(sequence); return get(sequence); } /** * Determines if the event for a given sequence is currently available. * *

Note that this does not guarantee that event will still be available * on the next interaction with the RingBuffer. For example, it is not * necessarily safe to write code like this: * *

{@code
     * if (ringBuffer.isAvailable(sequence))
     * {
     *     final E e = ringBuffer.get(sequence);
     *     // ...do something with e
     * }
     * }
* *

because there is a race between the reading thread and the writing thread. * *

This method will also return false when querying for sequences that are * behind the ring buffer's wrap point. * * @param sequence The sequence to identify the entry. * @return If the event published with the given sequence number is currently available. */ public boolean isAvailable(final long sequence) { return sequencer.isAvailable(sequence); } /** * Add the specified gating sequences to this instance of the Disruptor. They will * safely and atomically added to the list of gating sequences. * * @param gatingSequences The sequences to add. */ public void addGatingSequences(final Sequence... gatingSequences) { sequencer.addGatingSequences(gatingSequences); } /** * Get the minimum sequence value from all of the gating sequences * added to this ringBuffer. * * @return The minimum gating sequence or the cursor sequence if * no sequences have been added. */ public long getMinimumGatingSequence() { return sequencer.getMinimumSequence(); } /** * Remove the specified sequence from this ringBuffer. * * @param sequence to be removed. * @return true if this sequence was found, false otherwise. */ public boolean removeGatingSequence(final Sequence sequence) { return sequencer.removeGatingSequence(sequence); } /** * Create a new SequenceBarrier to be used by an EventProcessor to track which messages * are available to be read from the ring buffer given a list of sequences to track. * * @param sequencesToTrack the additional sequences to track * @return A sequence barrier that will track the specified sequences. * @see SequenceBarrier */ public SequenceBarrier newBarrier(final Sequence... sequencesToTrack) { return sequencer.newBarrier(sequencesToTrack); } /** * Creates an event poller for this ring buffer gated on the supplied sequences. * * @param gatingSequences to be gated on. * @return A poller that will gate on this ring buffer and the supplied sequences. */ public EventPoller newPoller(final Sequence... gatingSequences) { return sequencer.newPoller(this, gatingSequences); } /** * Get the current cursor value for the ring buffer. The actual value received * will depend on the type of {@link Sequencer} that is being used. * * @see MultiProducerSequencer * @see SingleProducerSequencer */ @Override public long getCursor() { return sequencer.getCursor(); } /** * The size of the buffer. * * @return size of buffer */ @Override public int getBufferSize() { return bufferSize; } /** * Given specified requiredCapacity determines if that amount of space * is available. Note, you can not assume that if this method returns true * that a call to {@link RingBuffer#next()} will not block. Especially true if this * ring buffer is set up to handle multiple producers. * * @param requiredCapacity The capacity to check for. * @return true If the specified requiredCapacity is available * false if not. */ @Override public boolean hasAvailableCapacity(final int requiredCapacity) { return sequencer.hasAvailableCapacity(requiredCapacity); } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslator) */ @Override public void publishEvent(final EventTranslator translator) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslator) */ @Override public boolean tryPublishEvent(final EventTranslator translator) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorOneArg, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorOneArg, A) */ @Override public void publishEvent(final EventTranslatorOneArg translator, final A arg0) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorOneArg, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorOneArg, A) */ @Override public boolean tryPublishEvent(final EventTranslatorOneArg translator, final A arg0) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorTwoArg, Object, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorTwoArg, A, B) */ @Override public void publishEvent(final EventTranslatorTwoArg translator, final A arg0, final B arg1) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0, arg1); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorTwoArg, Object, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorTwoArg, A, B) */ @Override public boolean tryPublishEvent(final EventTranslatorTwoArg translator, final A arg0, final B arg1) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0, arg1); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorThreeArg, Object, Object, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorThreeArg, A, B, C) */ @Override public void publishEvent(final EventTranslatorThreeArg translator, final A arg0, final B arg1, final C arg2) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0, arg1, arg2); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorThreeArg, Object, Object, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorThreeArg, A, B, C) */ @Override public boolean tryPublishEvent(final EventTranslatorThreeArg translator, final A arg0, final B arg1, final C arg2) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0, arg1, arg2); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorVararg, java.lang.Object...) */ @Override public void publishEvent(final EventTranslatorVararg translator, final Object... args) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, args); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorVararg, java.lang.Object...) */ @Override public boolean tryPublishEvent(final EventTranslatorVararg translator, final Object... args) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, args); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslator[]) */ @Override public void publishEvents(final EventTranslator[] translators) { publishEvents(translators, 0, translators.length); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslator[], int, int) */ @Override public void publishEvents(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBounds(translators, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translators, batchStartsAt, batchSize, finalSequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslator[]) */ @Override public boolean tryPublishEvents(final EventTranslator[] translators) { return tryPublishEvents(translators, 0, translators.length); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslator[], int, int) */ @Override public boolean tryPublishEvents(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBounds(translators, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translators, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, A[]) */ @Override public void publishEvents(final EventTranslatorOneArg translator, final A[] arg0) { publishEvents(translator, 0, arg0.length, arg0); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, A[]) */ @Override public void publishEvents(final EventTranslatorOneArg translator, final int batchStartsAt, final int batchSize, final A[] arg0) { checkBounds(arg0, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, batchStartsAt, batchSize, finalSequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, A[]) */ @Override public boolean tryPublishEvents(final EventTranslatorOneArg translator, final A[] arg0) { return tryPublishEvents(translator, 0, arg0.length, arg0); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, A[]) */ @Override public boolean tryPublishEvents( final EventTranslatorOneArg translator, final int batchStartsAt, final int batchSize, final A[] arg0) { checkBounds(arg0, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, A[], B[]) */ @Override public void publishEvents(final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1) { publishEvents(translator, 0, arg0.length, arg0, arg1); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, A[], B[]) */ @Override public void publishEvents( final EventTranslatorTwoArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1) { checkBounds(arg0, arg1, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, arg1, batchStartsAt, batchSize, finalSequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, A[], B[]) */ @Override public boolean tryPublishEvents(final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1) { return tryPublishEvents(translator, 0, arg0.length, arg0, arg1); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, A[], B[]) */ @Override public boolean tryPublishEvents( final EventTranslatorTwoArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1) { checkBounds(arg0, arg1, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, arg1, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, A[], B[], C[]) */ @Override public void publishEvents(final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2) { publishEvents(translator, 0, arg0.length, arg0, arg1, arg2); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, A[], B[], C[]) */ @Override public void publishEvents( final EventTranslatorThreeArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1, final C[] arg2) { checkBounds(arg0, arg1, arg2, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, arg1, arg2, batchStartsAt, batchSize, finalSequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, A[], B[], C[]) */ @Override public boolean tryPublishEvents( final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2) { return tryPublishEvents(translator, 0, arg0.length, arg0, arg1, arg2); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, A[], B[], C[]) */ @Override public boolean tryPublishEvents( final EventTranslatorThreeArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1, final C[] arg2) { checkBounds(arg0, arg1, arg2, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, arg1, arg2, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorVararg, java.lang.Object[][]) */ @Override public void publishEvents(final EventTranslatorVararg translator, final Object[]... args) { publishEvents(translator, 0, args.length, args); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorVararg, int, int, java.lang.Object[][]) */ @Override public void publishEvents(final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final Object[]... args) { checkBounds(batchStartsAt, batchSize, args); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, batchStartsAt, batchSize, finalSequence, args); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorVararg, java.lang.Object[][]) */ @Override public boolean tryPublishEvents(final EventTranslatorVararg translator, final Object[]... args) { return tryPublishEvents(translator, 0, args.length, args); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorVararg, int, int, java.lang.Object[][]) */ @Override public boolean tryPublishEvents( final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final Object[]... args) { checkBounds(args, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, batchStartsAt, batchSize, finalSequence, args); return true; } catch (InsufficientCapacityException e) { return false; } } /** * Publish the specified sequence. This action marks this particular * message as being available to be read. * * @param sequence the sequence to publish. */ @Override public void publish(final long sequence) { sequencer.publish(sequence); } /** * Publish the specified sequences. This action marks these particular * messages as being available to be read. * * @param lo the lowest sequence number to be published * @param hi the highest sequence number to be published * @see Sequencer#next(int) */ @Override public void publish(final long lo, final long hi) { sequencer.publish(lo, hi); } /** * Get the remaining capacity for this ringBuffer. * * @return The number of slots remaining. */ @Override public long remainingCapacity() { return sequencer.remainingCapacity(); } private void checkBounds(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(translators, batchStartsAt, batchSize); } private void checkBatchSizing(final int batchStartsAt, final int batchSize) { if (batchStartsAt < 0 || batchSize < 0) { throw new IllegalArgumentException("Both batchStartsAt and batchSize must be positive but got: batchStartsAt " + batchStartsAt + " and batchSize " + batchSize); } else if (batchSize > bufferSize) { throw new IllegalArgumentException("The ring buffer cannot accommodate " + batchSize + " it only has space for " + bufferSize + " entities."); } } private void checkBounds(final A[] arg0, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); } private void checkBounds(final A[] arg0, final B[] arg1, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); batchOverRuns(arg1, batchStartsAt, batchSize); } private void checkBounds( final A[] arg0, final B[] arg1, final C[] arg2, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); batchOverRuns(arg1, batchStartsAt, batchSize); batchOverRuns(arg2, batchStartsAt, batchSize); } private void checkBounds(final int batchStartsAt, final int batchSize, final Object[][] args) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(args, batchStartsAt, batchSize); } private void batchOverRuns(final A[] arg0, final int batchStartsAt, final int batchSize) { if (batchStartsAt + batchSize > arg0.length) { throw new IllegalArgumentException( "A batchSize of: " + batchSize + " with batchStatsAt of: " + batchStartsAt + " will overrun the available number of arguments: " + (arg0.length - batchStartsAt)); } } private void translateAndPublish(final EventTranslator translator, final long sequence) { try { translator.translateTo(get(sequence), sequence); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorOneArg translator, final long sequence, final A arg0) { try { translator.translateTo(get(sequence), sequence, arg0); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorTwoArg translator, final long sequence, final A arg0, final B arg1) { try { translator.translateTo(get(sequence), sequence, arg0, arg1); } finally { sequencer.publish(sequence); } } private void translateAndPublish( final EventTranslatorThreeArg translator, final long sequence, final A arg0, final B arg1, final C arg2) { try { translator.translateTo(get(sequence), sequence, arg0, arg1, arg2); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorVararg translator, final long sequence, final Object... args) { try { translator.translateTo(get(sequence), sequence, args); } finally { sequencer.publish(sequence); } } private void translateAndPublishBatch( final EventTranslator[] translators, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { final EventTranslator translator = translators[i]; translator.translateTo(get(sequence), sequence++); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorOneArg translator, final A[] arg0, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i], arg1[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i], arg1[i], arg2[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final long finalSequence, final Object[][] args) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, args[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } @Override public String toString() { return "RingBuffer{" + "bufferSize=" + bufferSize + ", sequencer=" + sequencer + "}"; } } ================================================ FILE: src/main/java/com/lmax/disruptor/Sequence.java ================================================ package com.lmax.disruptor; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; class LhsPadding { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; } class Value extends LhsPadding { protected long value; } class RhsPadding extends Value { protected byte p90, p91, p92, p93, p94, p95, p96, p97, p100, p101, p102, p103, p104, p105, p106, p107, p110, p111, p112, p113, p114, p115, p116, p117, p120, p121, p122, p123, p124, p125, p126, p127, p130, p131, p132, p133, p134, p135, p136, p137, p140, p141, p142, p143, p144, p145, p146, p147, p150, p151, p152, p153, p154, p155, p156, p157; } /** * Concurrent sequence class used for tracking the progress of * the ring buffer and event processors. Support a number * of concurrent operations including CAS and order writes. * *

Also attempts to be more efficient with regards to false * sharing by adding padding around the volatile field. */ public class Sequence extends RhsPadding { static final long INITIAL_VALUE = -1L; private static final VarHandle VALUE_FIELD; static { try { VALUE_FIELD = MethodHandles.lookup().in(Sequence.class) .findVarHandle(Sequence.class, "value", long.class); } catch (final Exception e) { throw new RuntimeException(e); } } /** * Create a sequence initialised to -1. */ public Sequence() { this(INITIAL_VALUE); } /** * Create a sequence with a specified initial value. * * @param initialValue The initial value for this sequence. */ public Sequence(final long initialValue) { VarHandle.releaseFence(); this.value = initialValue; } /** * Perform a volatile read of this sequence's value. * * @return The current value of the sequence. */ public long get() { long value = this.value; VarHandle.acquireFence(); return value; } /** * Perform an ordered write of this sequence. The intent is * a Store/Store barrier between this write and any previous * store. * * @param value The new value for the sequence. */ public void set(final long value) { VarHandle.releaseFence(); this.value = value; } /** * Performs a volatile write of this sequence. The intent is * a Store/Store barrier between this write and any previous * write and a Store/Load barrier between this write and any * subsequent volatile read. * * @param value The new value for the sequence. */ public void setVolatile(final long value) { VarHandle.releaseFence(); this.value = value; VarHandle.fullFence(); } /** * Perform a compare and set operation on the sequence. * * @param expectedValue The expected current value. * @param newValue The value to update to. * @return true if the operation succeeds, false otherwise. */ public boolean compareAndSet(final long expectedValue, final long newValue) { return VALUE_FIELD.compareAndSet(this, expectedValue, newValue); } /** * Atomically increment the sequence by one. * * @return The value after the increment */ public long incrementAndGet() { return addAndGet(1); } /** * Atomically add the supplied value. * * @param increment The value to add to the sequence. * @return The value after the increment. */ public long addAndGet(final long increment) { return (long) VALUE_FIELD.getAndAdd(this, increment) + increment; } /** * Perform an atomic getAndAdd operation on the sequence. * * @param increment The value to add to the sequence. * @return the value before increment */ public long getAndAdd(final long increment) { return (long) VALUE_FIELD.getAndAdd(this, increment); } @Override public String toString() { return Long.toString(get()); } } ================================================ FILE: src/main/java/com/lmax/disruptor/SequenceBarrier.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Coordination barrier for tracking the cursor for publishers and sequence of * dependent {@link EventProcessor}s for processing a data structure */ public interface SequenceBarrier { /** * Wait for the given sequence to be available for consumption. * * @param sequence to wait for * @return the sequence up to which is available * @throws AlertException if a status change has occurred for the Disruptor * @throws InterruptedException if the thread needs awaking on a condition variable. * @throws TimeoutException if a timeout occurs while waiting for the supplied sequence. */ long waitFor(long sequence) throws AlertException, InterruptedException, TimeoutException; /** * Get the current cursor value that can be read. * * @return value of the cursor for entries that have been published. */ long getCursor(); /** * The current alert status for the barrier. * * @return true if in alert otherwise false. */ boolean isAlerted(); /** * Alert the {@link EventProcessor}s of a status change and stay in this status until cleared. */ void alert(); /** * Clear the current alert status. */ void clearAlert(); /** * Check if an alert has been raised and throw an {@link AlertException} if it has. * * @throws AlertException if alert has been raised. */ void checkAlert() throws AlertException; } ================================================ FILE: src/main/java/com/lmax/disruptor/SequenceGroup.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.util.Util; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** * A {@link Sequence} group that can dynamically have {@link Sequence}s added and removed while being * thread safe. * *

The {@link SequenceGroup#get()} and {@link SequenceGroup#set(long)} methods are lock free and can be * concurrently be called with the {@link SequenceGroup#add(Sequence)} and {@link SequenceGroup#remove(Sequence)}. */ public final class SequenceGroup extends Sequence { private static final AtomicReferenceFieldUpdater SEQUENCE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(SequenceGroup.class, Sequence[].class, "sequences"); private volatile Sequence[] sequences = new Sequence[0]; /** * Default Constructor */ public SequenceGroup() { super(-1); } /** * Get the minimum sequence value for the group. * * @return the minimum sequence value for the group. */ @Override public long get() { return Util.getMinimumSequence(sequences); } /** * Set all {@link Sequence}s in the group to a given value. * * @param value to set the group of sequences to. */ @Override public void set(final long value) { final Sequence[] sequences = this.sequences; for (Sequence sequence : sequences) { sequence.set(value); } } /** * Add a {@link Sequence} into this aggregate. This should only be used during * initialisation. Use {@link SequenceGroup#addWhileRunning(Cursored, Sequence)} * * @param sequence to be added to the aggregate. * @see SequenceGroup#addWhileRunning(Cursored, Sequence) */ public void add(final Sequence sequence) { Sequence[] oldSequences; Sequence[] newSequences; do { oldSequences = sequences; final int oldSize = oldSequences.length; newSequences = new Sequence[oldSize + 1]; System.arraycopy(oldSequences, 0, newSequences, 0, oldSize); newSequences[oldSize] = sequence; } while (!SEQUENCE_UPDATER.compareAndSet(this, oldSequences, newSequences)); } /** * Remove the first occurrence of the {@link Sequence} from this aggregate. * * @param sequence to be removed from this aggregate. * @return true if the sequence was removed otherwise false. */ public boolean remove(final Sequence sequence) { return SequenceGroups.removeSequence(this, SEQUENCE_UPDATER, sequence); } /** * Get the size of the group. * * @return the size of the group. */ public int size() { return sequences.length; } /** * Adds a sequence to the sequence group after threads have started to publish to * the Disruptor. It will set the sequences to cursor value of the ringBuffer * just after adding them. This should prevent any nasty rewind/wrapping effects. * * @param cursored The data structure that the owner of this sequence group will * be pulling it's events from. * @param sequence The sequence to add. */ public void addWhileRunning(final Cursored cursored, final Sequence sequence) { SequenceGroups.addSequences(this, SEQUENCE_UPDATER, cursored, sequence); } } ================================================ FILE: src/main/java/com/lmax/disruptor/SequenceGroups.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import static java.util.Arrays.copyOf; /** * Provides static methods for managing a {@link SequenceGroup} object. */ class SequenceGroups { static void addSequences( final T holder, final AtomicReferenceFieldUpdater updater, final Cursored cursor, final Sequence... sequencesToAdd) { long cursorSequence; Sequence[] updatedSequences; Sequence[] currentSequences; do { currentSequences = updater.get(holder); updatedSequences = copyOf(currentSequences, currentSequences.length + sequencesToAdd.length); cursorSequence = cursor.getCursor(); int index = currentSequences.length; for (Sequence sequence : sequencesToAdd) { sequence.set(cursorSequence); updatedSequences[index++] = sequence; } } while (!updater.compareAndSet(holder, currentSequences, updatedSequences)); cursorSequence = cursor.getCursor(); for (Sequence sequence : sequencesToAdd) { sequence.set(cursorSequence); } } static boolean removeSequence( final T holder, final AtomicReferenceFieldUpdater sequenceUpdater, final Sequence sequence) { int numToRemove; Sequence[] oldSequences; Sequence[] newSequences; do { oldSequences = sequenceUpdater.get(holder); numToRemove = countMatching(oldSequences, sequence); if (0 == numToRemove) { break; } final int oldSize = oldSequences.length; newSequences = new Sequence[oldSize - numToRemove]; for (int i = 0, pos = 0; i < oldSize; i++) { final Sequence testSequence = oldSequences[i]; if (sequence != testSequence) { newSequences[pos++] = testSequence; } } } while (!sequenceUpdater.compareAndSet(holder, oldSequences, newSequences)); return numToRemove != 0; } private static int countMatching(final T[] values, final T toMatch) { int numToRemove = 0; for (T value : values) { if (value == toMatch) // Specifically uses identity { numToRemove++; } } return numToRemove; } } ================================================ FILE: src/main/java/com/lmax/disruptor/Sequenced.java ================================================ package com.lmax.disruptor; /** * Operations related to the sequencing of items in a {@link RingBuffer}. * See the two child interfaces, {@link Sequencer} and {@link EventSequencer} for more details. */ public interface Sequenced { /** * The capacity of the data structure to hold entries. * * @return the size of the RingBuffer. */ int getBufferSize(); /** * Has the buffer got capacity to allocate another sequence. This is a concurrent * method so the response should only be taken as an indication of available capacity. * * @param requiredCapacity in the buffer * @return true if the buffer has the capacity to allocate the next sequence otherwise false. */ boolean hasAvailableCapacity(int requiredCapacity); /** * Get the remaining capacity for this sequencer. * * @return The number of slots remaining. */ long remainingCapacity(); /** * Claim the next event in sequence for publishing. * * @return the claimed sequence value */ long next(); /** * Claim the next n events in sequence for publishing. This is for batch event producing. Using batch producing * requires a little care and some math. *

     * int n = 10;
     * long hi = sequencer.next(n);
     * long lo = hi - (n - 1);
     * for (long sequence = lo; sequence <= hi; sequence++) {
     *     // Do work.
     * }
     * sequencer.publish(lo, hi);
     * 
* * @param n the number of sequences to claim * @return the highest claimed sequence value */ long next(int n); /** * Attempt to claim the next event in sequence for publishing. Will return the * number of the slot if there is at least requiredCapacity slots * available. * * @return the claimed sequence value * @throws InsufficientCapacityException thrown if there is no space available in the ring buffer. */ long tryNext() throws InsufficientCapacityException; /** * Attempt to claim the next n events in sequence for publishing. Will return the * highest numbered slot if there is at least requiredCapacity slots * available. Have a look at {@link Sequencer#next()} for a description on how to * use this method. * * @param n the number of sequences to claim * @return the claimed sequence value * @throws InsufficientCapacityException thrown if there is no space available in the ring buffer. */ long tryNext(int n) throws InsufficientCapacityException; /** * Publishes a sequence. Call when the event has been filled. * * @param sequence the sequence to be published. */ void publish(long sequence); /** * Batch publish sequences. Called when all of the events have been filled. * * @param lo first sequence number to publish * @param hi last sequence number to publish */ void publish(long lo, long hi); } ================================================ FILE: src/main/java/com/lmax/disruptor/Sequencer.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Coordinates claiming sequences for access to a data structure while tracking dependent {@link Sequence}s */ public interface Sequencer extends Cursored, Sequenced { /** * Set to -1 as sequence starting point */ long INITIAL_CURSOR_VALUE = -1L; /** * Claim a specific sequence. Only used if initialising the ring buffer to * a specific value. * * @param sequence The sequence to initialise too. */ void claim(long sequence); /** * Confirms if a sequence is published and the event is available for use; non-blocking. * * @param sequence of the buffer to check * @return true if the sequence is available for use, false if not */ boolean isAvailable(long sequence); /** * Add the specified gating sequences to this instance of the Disruptor. They will * safely and atomically added to the list of gating sequences. * * @param gatingSequences The sequences to add. */ void addGatingSequences(Sequence... gatingSequences); /** * Remove the specified sequence from this sequencer. * * @param sequence to be removed. * @return true if this sequence was found, false otherwise. */ boolean removeGatingSequence(Sequence sequence); /** * Create a new SequenceBarrier to be used by an EventProcessor to track which messages * are available to be read from the ring buffer given a list of sequences to track. * * @param sequencesToTrack All of the sequences that the newly constructed barrier will wait on. * @return A sequence barrier that will track the specified sequences. * @see SequenceBarrier */ SequenceBarrier newBarrier(Sequence... sequencesToTrack); /** * Get the minimum sequence value from all of the gating sequences * added to this ringBuffer. * * @return The minimum gating sequence or the cursor sequence if * no sequences have been added. */ long getMinimumSequence(); /** * Get the highest sequence number that can be safely read from the ring buffer. Depending * on the implementation of the Sequencer this call may need to scan a number of values * in the Sequencer. The scan will range from nextSequence to availableSequence. If * there are no available values >= nextSequence the return value will be * nextSequence - 1. To work correctly a consumer should pass a value that * is 1 higher than the last sequence that was successfully processed. * * @param nextSequence The sequence to start scanning from. * @param availableSequence The sequence to scan to. * @return The highest value that can be safely read, will be at least nextSequence - 1. */ long getHighestPublishedSequence(long nextSequence, long availableSequence); /** * Creates an event poller from this sequencer * * @param provider from which events are drawn * @param gatingSequences sequences to be gated on * @param the type of the event * @return the event poller */ EventPoller newPoller(DataProvider provider, Sequence... gatingSequences); } ================================================ FILE: src/main/java/com/lmax/disruptor/SimpleBatchRewindStrategy.java ================================================ package com.lmax.disruptor; /** * Batch rewind strategy that always rewinds */ public class SimpleBatchRewindStrategy implements BatchRewindStrategy { @Override public RewindAction handleRewindException(final RewindableException e, final int retriesAttempted) { return RewindAction.REWIND; } } ================================================ FILE: src/main/java/com/lmax/disruptor/SingleProducerSequencer.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.util.Util; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.LockSupport; abstract class SingleProducerSequencerPad extends AbstractSequencer { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; SingleProducerSequencerPad(final int bufferSize, final WaitStrategy waitStrategy) { super(bufferSize, waitStrategy); } } abstract class SingleProducerSequencerFields extends SingleProducerSequencerPad { SingleProducerSequencerFields(final int bufferSize, final WaitStrategy waitStrategy) { super(bufferSize, waitStrategy); } /** * Set to -1 as sequence starting point */ long nextValue = Sequence.INITIAL_VALUE; long cachedValue = Sequence.INITIAL_VALUE; } /** * Coordinator for claiming sequences for access to a data structure while tracking dependent {@link Sequence}s. * Not safe for use from multiple threads as it does not implement any barriers. * *

* Note on {@link Sequencer#getCursor()}: With this sequencer the cursor value is updated after the call * to {@link Sequencer#publish(long)} is made. */ public final class SingleProducerSequencer extends SingleProducerSequencerFields { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; /** * Construct a Sequencer with the selected wait strategy and buffer size. * * @param bufferSize the size of the buffer that this will sequence over. * @param waitStrategy for those waiting on sequences. */ public SingleProducerSequencer(final int bufferSize, final WaitStrategy waitStrategy) { super(bufferSize, waitStrategy); } /** * @see Sequencer#hasAvailableCapacity(int) */ @Override public boolean hasAvailableCapacity(final int requiredCapacity) { return hasAvailableCapacity(requiredCapacity, false); } private boolean hasAvailableCapacity(final int requiredCapacity, final boolean doStore) { long nextValue = this.nextValue; long wrapPoint = (nextValue + requiredCapacity) - bufferSize; long cachedGatingSequence = this.cachedValue; if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue) { if (doStore) { cursor.setVolatile(nextValue); // StoreLoad fence } long minSequence = Util.getMinimumSequence(gatingSequences, nextValue); this.cachedValue = minSequence; if (wrapPoint > minSequence) { return false; } } return true; } /** * @see Sequencer#next() */ @Override public long next() { return next(1); } /** * @see Sequencer#next(int) */ @Override public long next(final int n) { assert sameThread() : "Accessed by two threads - use ProducerType.MULTI!"; if (n < 1 || n > bufferSize) { throw new IllegalArgumentException("n must be > 0 and < bufferSize"); } long nextValue = this.nextValue; long nextSequence = nextValue + n; long wrapPoint = nextSequence - bufferSize; long cachedGatingSequence = this.cachedValue; if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue) { cursor.setVolatile(nextValue); // StoreLoad fence long minSequence; while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue))) { LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin? } this.cachedValue = minSequence; } this.nextValue = nextSequence; return nextSequence; } /** * @see Sequencer#tryNext() */ @Override public long tryNext() throws InsufficientCapacityException { return tryNext(1); } /** * @see Sequencer#tryNext(int) */ @Override public long tryNext(final int n) throws InsufficientCapacityException { if (n < 1) { throw new IllegalArgumentException("n must be > 0"); } if (!hasAvailableCapacity(n, true)) { throw InsufficientCapacityException.INSTANCE; } long nextSequence = this.nextValue += n; return nextSequence; } /** * @see Sequencer#remainingCapacity() */ @Override public long remainingCapacity() { long nextValue = this.nextValue; long consumed = Util.getMinimumSequence(gatingSequences, nextValue); long produced = nextValue; return getBufferSize() - (produced - consumed); } /** * @see Sequencer#claim(long) */ @Override public void claim(final long sequence) { this.nextValue = sequence; } /** * @see Sequencer#publish(long) */ @Override public void publish(final long sequence) { cursor.set(sequence); waitStrategy.signalAllWhenBlocking(); } /** * @see Sequencer#publish(long, long) */ @Override public void publish(final long lo, final long hi) { publish(hi); } /** * @see Sequencer#isAvailable(long) */ @Override public boolean isAvailable(final long sequence) { final long currentSequence = cursor.get(); return sequence <= currentSequence && sequence > currentSequence - bufferSize; } @Override public long getHighestPublishedSequence(final long lowerBound, final long availableSequence) { return availableSequence; } @Override public String toString() { return "SingleProducerSequencer{" + "bufferSize=" + bufferSize + ", waitStrategy=" + waitStrategy + ", cursor=" + cursor + ", gatingSequences=" + Arrays.toString(gatingSequences) + '}'; } private boolean sameThread() { return ProducerThreadAssertion.isSameThreadProducingTo(this); } /** * Only used when assertions are enabled. */ private static class ProducerThreadAssertion { /** * Tracks the threads publishing to {@code SingleProducerSequencer}s to identify if more than one * thread accesses any {@code SingleProducerSequencer}. * I.e. it helps developers detect early if they use the wrong * {@link com.lmax.disruptor.dsl.ProducerType}. */ private static final Map PRODUCERS = new HashMap<>(); public static boolean isSameThreadProducingTo(final SingleProducerSequencer singleProducerSequencer) { synchronized (PRODUCERS) { final Thread currentThread = Thread.currentThread(); if (!PRODUCERS.containsKey(singleProducerSequencer)) { PRODUCERS.put(singleProducerSequencer, currentThread); } return PRODUCERS.get(singleProducerSequencer).equals(currentThread); } } } } ================================================ FILE: src/main/java/com/lmax/disruptor/SleepingWaitStrategy.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import java.util.concurrent.locks.LockSupport; /** * Sleeping strategy that initially spins, then uses a Thread.yield(), and * eventually sleep (LockSupport.parkNanos(n)) for the minimum * number of nanos the OS and JVM will allow while the * {@link com.lmax.disruptor.EventProcessor}s are waiting on a barrier. * *

This strategy is a good compromise between performance and CPU resource. * Latency spikes can occur after quiet periods. It will also reduce the impact * on the producing thread as it will not need signal any conditional variables * to wake up the event handling thread. */ public final class SleepingWaitStrategy implements WaitStrategy { private static final int SPIN_THRESHOLD = 100; private static final int DEFAULT_RETRIES = 200; private static final long DEFAULT_SLEEP = 100; private final int retries; private final long sleepTimeNs; /** * Provides a sleeping wait strategy with the default retry and sleep settings */ public SleepingWaitStrategy() { this(DEFAULT_RETRIES, DEFAULT_SLEEP); } /** * @param retries How many times the strategy should retry before sleeping */ public SleepingWaitStrategy(final int retries) { this(retries, DEFAULT_SLEEP); } /** * @param retries How many times the strategy should retry before sleeping * @param sleepTimeNs How long the strategy should sleep, in nanoseconds */ public SleepingWaitStrategy(final int retries, final long sleepTimeNs) { this.retries = retries; this.sleepTimeNs = sleepTimeNs; } @Override public long waitFor( final long sequence, final Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException { long availableSequence; int counter = retries; while ((availableSequence = dependentSequence.get()) < sequence) { counter = applyWaitMethod(barrier, counter); } return availableSequence; } @Override public void signalAllWhenBlocking() { } private int applyWaitMethod(final SequenceBarrier barrier, final int counter) throws AlertException { barrier.checkAlert(); if (counter > SPIN_THRESHOLD) { return counter - 1; } else if (counter > 0) { Thread.yield(); return counter - 1; } else { LockSupport.parkNanos(sleepTimeNs); } return counter; } } ================================================ FILE: src/main/java/com/lmax/disruptor/TimeoutBlockingWaitStrategy.java ================================================ package com.lmax.disruptor; import java.util.concurrent.TimeUnit; import static com.lmax.disruptor.util.Util.awaitNanos; /** * Blocking strategy that uses a lock and condition variable for {@link EventProcessor}s waiting on a barrier. * However it will periodically wake up if it has been idle for specified period by throwing a * {@link TimeoutException}. To make use of this, the event handler class should override * {@link EventHandler#onTimeout(long)}, which the {@link BatchEventProcessor} will call if the timeout occurs. * *

This strategy can be used when throughput and low-latency are not as important as CPU resource. */ public class TimeoutBlockingWaitStrategy implements WaitStrategy { private final Object mutex = new Object(); private final long timeoutInNanos; /** * @param timeout how long to wait before waking up * @param units the unit in which timeout is specified */ public TimeoutBlockingWaitStrategy(final long timeout, final TimeUnit units) { timeoutInNanos = units.toNanos(timeout); } @Override public long waitFor( final long sequence, final Sequence cursorSequence, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException, TimeoutException { long timeoutNanos = timeoutInNanos; long availableSequence; if (cursorSequence.get() < sequence) { synchronized (mutex) { while (cursorSequence.get() < sequence) { barrier.checkAlert(); timeoutNanos = awaitNanos(mutex, timeoutNanos); if (timeoutNanos <= 0) { throw TimeoutException.INSTANCE; } } } } while ((availableSequence = dependentSequence.get()) < sequence) { barrier.checkAlert(); } return availableSequence; } @Override public void signalAllWhenBlocking() { synchronized (mutex) { mutex.notifyAll(); } } @Override public String toString() { return "TimeoutBlockingWaitStrategy{" + "mutex=" + mutex + ", timeoutInNanos=" + timeoutInNanos + '}'; } } ================================================ FILE: src/main/java/com/lmax/disruptor/TimeoutException.java ================================================ package com.lmax.disruptor; /** * Wait strategies may throw this Exception to inform callers that a * message has not been detected within a specific time window. * For efficiency, a single instance is provided. */ @SuppressWarnings({"serial", "lgtm[java/non-sync-override]"}) public final class TimeoutException extends Exception { /** * The efficiency saving singleton instance */ public static final TimeoutException INSTANCE = new TimeoutException(); private TimeoutException() { // Singleton } @Override public Throwable fillInStackTrace() { return this; } } ================================================ FILE: src/main/java/com/lmax/disruptor/WaitStrategy.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Strategy employed for making {@link EventProcessor}s wait on a cursor {@link Sequence}. */ public interface WaitStrategy { /** * Wait for the given sequence to be available. It is possible for this method to return a value * less than the sequence number supplied depending on the implementation of the WaitStrategy. A common * use for this is to signal a timeout. Any EventProcessor that is using a WaitStrategy to get notifications * about message becoming available should remember to handle this case. The {@link BatchEventProcessor} explicitly * handles this case and will signal a timeout if required. * * @param sequence to be waited on. * @param cursor the main sequence from ringbuffer. Wait/notify strategies will * need this as it's the only sequence that is also notified upon update. * @param dependentSequence on which to wait. * @param barrier the processor is waiting on. * @return the sequence that is available which may be greater than the requested sequence. * @throws AlertException if the status of the Disruptor has changed. * @throws InterruptedException if the thread is interrupted. * @throws TimeoutException if a timeout occurs before waiting completes (not used by some strategies) */ long waitFor(long sequence, Sequence cursor, Sequence dependentSequence, SequenceBarrier barrier) throws AlertException, InterruptedException, TimeoutException; /** * Implementations should signal the waiting {@link EventProcessor}s that the cursor has advanced. */ void signalAllWhenBlocking(); } ================================================ FILE: src/main/java/com/lmax/disruptor/YieldingWaitStrategy.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Yielding strategy that uses a Thread.yield() for {@link com.lmax.disruptor.EventProcessor}s waiting on a barrier * after an initially spinning. * *

This strategy will use 100% CPU, but will more readily give up the CPU than a busy spin strategy if other threads * require CPU resource. */ public final class YieldingWaitStrategy implements WaitStrategy { private static final int SPIN_TRIES = 100; @Override public long waitFor( final long sequence, final Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException { long availableSequence; int counter = SPIN_TRIES; while ((availableSequence = dependentSequence.get()) < sequence) { counter = applyWaitMethod(barrier, counter); } return availableSequence; } @Override public void signalAllWhenBlocking() { } private int applyWaitMethod(final SequenceBarrier barrier, final int counter) throws AlertException { barrier.checkAlert(); if (0 == counter) { Thread.yield(); } else { return counter - 1; } return counter; } } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/ConsumerInfo.java ================================================ package com.lmax.disruptor.dsl; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import java.util.concurrent.ThreadFactory; interface ConsumerInfo { Sequence[] getSequences(); SequenceBarrier getBarrier(); boolean isEndOfChain(); void start(ThreadFactory threadFactory); void halt(); void markAsUsedInBarrier(); boolean isRunning(); } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/ConsumerRepository.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.EventHandlerIdentity; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import java.util.ArrayList; import java.util.Collection; import java.util.IdentityHashMap; import java.util.Map; import java.util.concurrent.ThreadFactory; /** * Provides a repository mechanism to associate {@link EventHandler}s with {@link EventProcessor}s */ class ConsumerRepository { private final Map eventProcessorInfoByEventHandler = new IdentityHashMap<>(); private final Map eventProcessorInfoBySequence = new IdentityHashMap<>(); private final Collection consumerInfos = new ArrayList<>(); public void add( final EventProcessor eventprocessor, final EventHandlerIdentity handlerIdentity, final SequenceBarrier barrier) { final EventProcessorInfo consumerInfo = new EventProcessorInfo(eventprocessor, barrier); eventProcessorInfoByEventHandler.put(handlerIdentity, consumerInfo); eventProcessorInfoBySequence.put(eventprocessor.getSequence(), consumerInfo); consumerInfos.add(consumerInfo); } public void add(final EventProcessor processor) { final EventProcessorInfo consumerInfo = new EventProcessorInfo(processor, null); eventProcessorInfoBySequence.put(processor.getSequence(), consumerInfo); consumerInfos.add(consumerInfo); } public void startAll(final ThreadFactory threadFactory) { consumerInfos.forEach(c -> c.start(threadFactory)); } public void haltAll() { consumerInfos.forEach(ConsumerInfo::halt); } public boolean hasBacklog(final long cursor, final boolean includeStopped) { for (ConsumerInfo consumerInfo : consumerInfos) { if ((includeStopped || consumerInfo.isRunning()) && consumerInfo.isEndOfChain()) { final Sequence[] sequences = consumerInfo.getSequences(); for (Sequence sequence : sequences) { if (cursor > sequence.get()) { return true; } } } } return false; } public EventProcessor getEventProcessorFor(final EventHandlerIdentity handlerIdentity) { final EventProcessorInfo eventprocessorInfo = getEventProcessorInfo(handlerIdentity); if (eventprocessorInfo == null) { throw new IllegalArgumentException("The event handler " + handlerIdentity + " is not processing events."); } return eventprocessorInfo.getEventProcessor(); } public Sequence getSequenceFor(final EventHandlerIdentity handlerIdentity) { return getEventProcessorFor(handlerIdentity).getSequence(); } public void unMarkEventProcessorsAsEndOfChain(final Sequence... barrierEventProcessors) { for (Sequence barrierEventProcessor : barrierEventProcessors) { getEventProcessorInfo(barrierEventProcessor).markAsUsedInBarrier(); } } public SequenceBarrier getBarrierFor(final EventHandlerIdentity handlerIdentity) { final ConsumerInfo consumerInfo = getEventProcessorInfo(handlerIdentity); return consumerInfo != null ? consumerInfo.getBarrier() : null; } private EventProcessorInfo getEventProcessorInfo(final EventHandlerIdentity handlerIdentity) { return eventProcessorInfoByEventHandler.get(handlerIdentity); } private ConsumerInfo getEventProcessorInfo(final Sequence barrierEventProcessor) { return eventProcessorInfoBySequence.get(barrierEventProcessor); } } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/Disruptor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.BatchRewindStrategy; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.EventHandlerIdentity; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.EventTranslator; import com.lmax.disruptor.EventTranslatorOneArg; import com.lmax.disruptor.EventTranslatorThreeArg; import com.lmax.disruptor.EventTranslatorTwoArg; import com.lmax.disruptor.ExceptionHandler; import com.lmax.disruptor.RewindableEventHandler; import com.lmax.disruptor.RewindableException; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.TimeoutException; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.util.Util; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * A DSL-style API for setting up the disruptor pattern around a ring buffer * (aka the Builder pattern). * *

A simple example of setting up the disruptor with two event handlers that * must process events in order: * *

 * Disruptor<MyEvent> disruptor = new Disruptor<MyEvent>(MyEvent.FACTORY, 32, Executors.newCachedThreadPool());
 * EventHandler<MyEvent> handler1 = new EventHandler<MyEvent>() { ... };
 * EventHandler<MyEvent> handler2 = new EventHandler<MyEvent>() { ... };
 * disruptor.handleEventsWith(handler1);
 * disruptor.after(handler1).handleEventsWith(handler2);
 *
 * RingBuffer ringBuffer = disruptor.start();
 * 
* * @param the type of event used. */ public class Disruptor { private final RingBuffer ringBuffer; private final ThreadFactory threadFactory; private final ConsumerRepository consumerRepository = new ConsumerRepository(); private final AtomicBoolean started = new AtomicBoolean(false); private ExceptionHandler exceptionHandler = new ExceptionHandlerWrapper<>(); /** * Create a new Disruptor. Will default to {@link com.lmax.disruptor.BlockingWaitStrategy} and * {@link ProducerType}.MULTI * * @param eventFactory the factory to create events in the ring buffer. * @param ringBufferSize the size of the ring buffer. * @param threadFactory a {@link ThreadFactory} to create threads to for processors. */ public Disruptor(final EventFactory eventFactory, final int ringBufferSize, final ThreadFactory threadFactory) { this(RingBuffer.createMultiProducer(eventFactory, ringBufferSize), threadFactory); } /** * Create a new Disruptor. * * @param eventFactory the factory to create events in the ring buffer. * @param ringBufferSize the size of the ring buffer, must be power of 2. * @param threadFactory a {@link ThreadFactory} to create threads for processors. * @param producerType the claim strategy to use for the ring buffer. * @param waitStrategy the wait strategy to use for the ring buffer. */ public Disruptor( final EventFactory eventFactory, final int ringBufferSize, final ThreadFactory threadFactory, final ProducerType producerType, final WaitStrategy waitStrategy) { this( RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy), threadFactory); } /** * Private constructor helper */ private Disruptor(final RingBuffer ringBuffer, final ThreadFactory threadFactory) { this.ringBuffer = ringBuffer; this.threadFactory = threadFactory; } /** *

Set up event handlers to handle events from the ring buffer. These handlers will process events * as soon as they become available, in parallel.

* *

This method can be used as the start of a chain. For example if the handler A must * process events before handler B:

*
dw.handleEventsWith(A).then(B);
* *

This call is additive, but generally should only be called once when setting up the Disruptor instance

* * @param handlers the event handlers that will process events. * @return a {@link EventHandlerGroup} that can be used to chain dependencies. */ @SuppressWarnings("varargs") @SafeVarargs public final EventHandlerGroup handleEventsWith(final EventHandler... handlers) { return createEventProcessors(new Sequence[0], handlers); } /** *

Set up event handlers to handle events from the ring buffer. These handlers will process events * as soon as they become available, in parallel.

* *

This method can be used as the start of a chain. For example if the handler A must * process events before handler B:

*
dw.handleEventsWith(A).then(B);
* *

This call is additive, but generally should only be called once when setting up the Disruptor instance

* * @param batchRewindStrategy a {@link BatchRewindStrategy} for customizing how to handle a {@link RewindableException}. * @param handlers the rewindable event handlers that will process events. * @return a {@link EventHandlerGroup} that can be used to chain dependencies. */ @SuppressWarnings("varargs") @SafeVarargs public final EventHandlerGroup handleEventsWith(final BatchRewindStrategy batchRewindStrategy, final RewindableEventHandler... handlers) { return createEventProcessors(new Sequence[0], batchRewindStrategy, handlers); } /** *

Set up custom event processors to handle events from the ring buffer. The Disruptor will * automatically start these processors when {@link #start()} is called.

* *

This method can be used as the start of a chain. For example if the handler A must * process events before handler B:

*
dw.handleEventsWith(A).then(B);
* *

Since this is the start of the chain, the processor factories will always be passed an empty Sequence * array, so the factory isn't necessary in this case. This method is provided for consistency with * {@link EventHandlerGroup#handleEventsWith(EventProcessorFactory...)} and {@link EventHandlerGroup#then(EventProcessorFactory...)} * which do have barrier sequences to provide.

* *

This call is additive, but generally should only be called once when setting up the Disruptor instance

* * @param eventProcessorFactories the event processor factories to use to create the event processors that will process events. * @return a {@link EventHandlerGroup} that can be used to chain dependencies. */ @SafeVarargs public final EventHandlerGroup handleEventsWith(final EventProcessorFactory... eventProcessorFactories) { final Sequence[] barrierSequences = new Sequence[0]; return createEventProcessors(barrierSequences, eventProcessorFactories); } /** *

Set up custom event processors to handle events from the ring buffer. The Disruptor will * automatically start this processors when {@link #start()} is called.

* *

This method can be used as the start of a chain. For example if the processor A must * process events before handler B:

*
dw.handleEventsWith(A).then(B);
* * @param processors the event processors that will process events. * @return a {@link EventHandlerGroup} that can be used to chain dependencies. */ public EventHandlerGroup handleEventsWith(final EventProcessor... processors) { for (final EventProcessor processor : processors) { consumerRepository.add(processor); } final Sequence[] sequences = Util.getSequencesFor(processors); ringBuffer.addGatingSequences(sequences); return new EventHandlerGroup<>(this, consumerRepository, sequences); } /** *

Specify an exception handler to be used for any future event handlers.

* *

Note that only event handlers set up after calling this method will use the exception handler.

* * @param exceptionHandler the exception handler to use for any future {@link EventProcessor}. * @deprecated This method only applies to future event handlers. Use setDefaultExceptionHandler instead which applies to existing and new event handlers. */ @Deprecated public void handleExceptionsWith(final ExceptionHandler exceptionHandler) { this.exceptionHandler = exceptionHandler; } /** *

Specify an exception handler to be used for event handlers and worker pools created by this Disruptor.

* *

The exception handler will be used by existing and future event handlers and worker pools created by this Disruptor instance.

* * @param exceptionHandler the exception handler to use. */ @SuppressWarnings("unchecked") public void setDefaultExceptionHandler(final ExceptionHandler exceptionHandler) { checkNotStarted(); if (!(this.exceptionHandler instanceof ExceptionHandlerWrapper)) { throw new IllegalStateException("setDefaultExceptionHandler can not be used after handleExceptionsWith"); } ((ExceptionHandlerWrapper) this.exceptionHandler).switchTo(exceptionHandler); } /** * Override the default exception handler for a specific handler. *
disruptorWizard.handleExceptionsIn(eventHandler).with(exceptionHandler);
* * @param eventHandler the event handler to set a different exception handler for. * @return an ExceptionHandlerSetting dsl object - intended to be used by chaining the with method call. */ public ExceptionHandlerSetting handleExceptionsFor(final EventHandlerIdentity eventHandler) { return new ExceptionHandlerSetting<>(eventHandler, consumerRepository); } /** *

Create a group of event handlers to be used as a dependency. * For example if the handler A must process events before handler B:

* *
dw.after(A).handleEventsWith(B);
* * @param handlers the event handlers, previously set up with {@link #handleEventsWith(EventHandler[])}, * that will form the barrier for subsequent handlers or processors. * @return an {@link EventHandlerGroup} that can be used to setup a dependency barrier over the specified event handlers. */ public final EventHandlerGroup after(final EventHandlerIdentity... handlers) { final Sequence[] sequences = new Sequence[handlers.length]; for (int i = 0, handlersLength = handlers.length; i < handlersLength; i++) { sequences[i] = consumerRepository.getSequenceFor(handlers[i]); } return new EventHandlerGroup<>(this, consumerRepository, sequences); } /** * Create a group of event processors to be used as a dependency. * * @param processors the event processors, previously set up with {@link #handleEventsWith(com.lmax.disruptor.EventProcessor...)}, * that will form the barrier for subsequent handlers or processors. * @return an {@link EventHandlerGroup} that can be used to setup a {@link SequenceBarrier} over the specified event processors. * @see #after(EventHandlerIdentity[]) */ public EventHandlerGroup after(final EventProcessor... processors) { return new EventHandlerGroup<>(this, consumerRepository, Util.getSequencesFor(processors)); } /** * Publish an event to the ring buffer. * * @param eventTranslator the translator that will load data into the event. */ public void publishEvent(final EventTranslator eventTranslator) { ringBuffer.publishEvent(eventTranslator); } /** * Publish an event to the ring buffer. * * @param
Class of the user supplied argument. * @param eventTranslator the translator that will load data into the event. * @param arg A single argument to load into the event */ public void publishEvent(final EventTranslatorOneArg eventTranslator, final A arg) { ringBuffer.publishEvent(eventTranslator, arg); } /** * Publish a batch of events to the ring buffer. * * @param Class of the user supplied argument. * @param eventTranslator the translator that will load data into the event. * @param arg An array single arguments to load into the events. One Per event. */ public void publishEvents(final EventTranslatorOneArg eventTranslator, final A[] arg) { ringBuffer.publishEvents(eventTranslator, arg); } /** * Publish an event to the ring buffer. * * @param Class of the user supplied argument. * @param Class of the user supplied argument. * @param eventTranslator the translator that will load data into the event. * @param arg0 The first argument to load into the event * @param arg1 The second argument to load into the event */ public void publishEvent(final EventTranslatorTwoArg eventTranslator, final A arg0, final B arg1) { ringBuffer.publishEvent(eventTranslator, arg0, arg1); } /** * Publish an event to the ring buffer. * * @param eventTranslator the translator that will load data into the event. * @param Class of the user supplied argument. * @param Class of the user supplied argument. * @param Class of the user supplied argument. * @param arg0 The first argument to load into the event * @param arg1 The second argument to load into the event * @param arg2 The third argument to load into the event */ public void publishEvent(final EventTranslatorThreeArg eventTranslator, final A arg0, final B arg1, final C arg2) { ringBuffer.publishEvent(eventTranslator, arg0, arg1, arg2); } /** *

Starts the event processors and returns the fully configured ring buffer.

* *

The ring buffer is set up to prevent overwriting any entry that is yet to * be processed by the slowest event processor.

* *

This method must only be called once after all event processors have been added.

* * @return the configured ring buffer. */ public RingBuffer start() { checkOnlyStartedOnce(); consumerRepository.startAll(threadFactory); return ringBuffer; } /** * Calls {@link com.lmax.disruptor.EventProcessor#halt()} on all of the event processors created via this disruptor. */ public void halt() { consumerRepository.haltAll(); } /** *

Waits until all events currently in the disruptor have been processed by all event processors * and then halts the processors. It is critical that publishing to the ring buffer has stopped * before calling this method, otherwise it may never return.

* *

This method will not shutdown the executor, nor will it await the final termination of the * processor threads.

*/ public void shutdown() { try { shutdown(-1, TimeUnit.MILLISECONDS); } catch (final TimeoutException e) { exceptionHandler.handleOnShutdownException(e); } } /** *

Waits until all events currently in the disruptor have been processed by all event processors * and then halts the processors.

* *

This method will not shutdown the executor, nor will it await the final termination of the * processor threads.

* * @param timeout the amount of time to wait for all events to be processed. -1 will give an infinite timeout * @param timeUnit the unit the timeOut is specified in * @throws TimeoutException if a timeout occurs before shutdown completes. */ public void shutdown(final long timeout, final TimeUnit timeUnit) throws TimeoutException { final long timeOutAt = System.nanoTime() + timeUnit.toNanos(timeout); while (hasBacklog()) { if (timeout >= 0 && System.nanoTime() > timeOutAt) { throw TimeoutException.INSTANCE; } // Busy spin } halt(); } /** * The {@link RingBuffer} used by this Disruptor. This is useful for creating custom * event processors if the behaviour of {@link BatchEventProcessor} is not suitable. * * @return the ring buffer used by this Disruptor. */ public RingBuffer getRingBuffer() { return ringBuffer; } /** * Get the value of the cursor indicating the published sequence. * * @return value of the cursor for events that have been published. */ public long getCursor() { return ringBuffer.getCursor(); } /** * The capacity of the data structure to hold entries. * * @return the size of the RingBuffer. * @see com.lmax.disruptor.Sequencer#getBufferSize() */ public long getBufferSize() { return ringBuffer.getBufferSize(); } /** * Get the event for a given sequence in the RingBuffer. * * @param sequence for the event. * @return event for the sequence. * @see RingBuffer#get(long) */ public T get(final long sequence) { return ringBuffer.get(sequence); } /** * Get the {@link SequenceBarrier} used by a specific handler. Note that the {@link SequenceBarrier} * may be shared by multiple event handlers. * * @param handler the handler to get the barrier for. * @return the SequenceBarrier used by handler. */ public SequenceBarrier getBarrierFor(final EventHandlerIdentity handler) { return consumerRepository.getBarrierFor(handler); } /** * Gets the sequence value for the specified event handlers. * * @param handler eventHandler to get the sequence for. * @return eventHandler's sequence */ public long getSequenceValueFor(final EventHandlerIdentity handler) { return consumerRepository.getSequenceFor(handler).get(); } /** * Confirms if all messages have been consumed by all event processors */ private boolean hasBacklog() { final long cursor = ringBuffer.getCursor(); return consumerRepository.hasBacklog(cursor, false); } /** * Checks if disruptor has been started * * @return true when start has been called on this instance; otherwise false */ public boolean hasStarted() { return started.get(); } EventHandlerGroup createEventProcessors( final Sequence[] barrierSequences, final EventHandler[] eventHandlers) { checkNotStarted(); final Sequence[] processorSequences = new Sequence[eventHandlers.length]; final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences); for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++) { final EventHandler eventHandler = eventHandlers[i]; final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build(ringBuffer, barrier, eventHandler); if (exceptionHandler != null) { batchEventProcessor.setExceptionHandler(exceptionHandler); } consumerRepository.add(batchEventProcessor, eventHandler, barrier); processorSequences[i] = batchEventProcessor.getSequence(); } updateGatingSequencesForNextInChain(barrierSequences, processorSequences); return new EventHandlerGroup<>(this, consumerRepository, processorSequences); } EventHandlerGroup createEventProcessors( final Sequence[] barrierSequences, final BatchRewindStrategy batchRewindStrategy, final RewindableEventHandler[] eventHandlers) { checkNotStarted(); final Sequence[] processorSequences = new Sequence[eventHandlers.length]; final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences); for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++) { final RewindableEventHandler eventHandler = eventHandlers[i]; final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build(ringBuffer, barrier, eventHandler, batchRewindStrategy); if (exceptionHandler != null) { batchEventProcessor.setExceptionHandler(exceptionHandler); } consumerRepository.add(batchEventProcessor, eventHandler, barrier); processorSequences[i] = batchEventProcessor.getSequence(); } updateGatingSequencesForNextInChain(barrierSequences, processorSequences); return new EventHandlerGroup<>(this, consumerRepository, processorSequences); } private void updateGatingSequencesForNextInChain(final Sequence[] barrierSequences, final Sequence[] processorSequences) { if (processorSequences.length > 0) { ringBuffer.addGatingSequences(processorSequences); for (final Sequence barrierSequence : barrierSequences) { ringBuffer.removeGatingSequence(barrierSequence); } consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences); } } EventHandlerGroup createEventProcessors( final Sequence[] barrierSequences, final EventProcessorFactory[] processorFactories) { final EventProcessor[] eventProcessors = new EventProcessor[processorFactories.length]; for (int i = 0; i < processorFactories.length; i++) { eventProcessors[i] = processorFactories[i].createEventProcessor(ringBuffer, barrierSequences); } return handleEventsWith(eventProcessors); } private void checkNotStarted() { if (started.get()) { throw new IllegalStateException("All event handlers must be added before calling starts."); } } private void checkOnlyStartedOnce() { if (!started.compareAndSet(false, true)) { throw new IllegalStateException("Disruptor.start() must only be called once."); } } @Override public String toString() { return "Disruptor{" + "ringBuffer=" + ringBuffer + ", started=" + started + ", threadFactory=" + threadFactory + '}'; } } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/EventHandlerGroup.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl; import com.lmax.disruptor.BatchRewindStrategy; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.RewindableEventHandler; import com.lmax.disruptor.RewindableException; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import java.util.Arrays; /** * A group of {@link EventProcessor}s used as part of the {@link Disruptor}. * * @param the type of entry used by the event processors. */ public class EventHandlerGroup { private final Disruptor disruptor; private final ConsumerRepository consumerRepository; private final Sequence[] sequences; EventHandlerGroup( final Disruptor disruptor, final ConsumerRepository consumerRepository, final Sequence[] sequences) { this.disruptor = disruptor; this.consumerRepository = consumerRepository; this.sequences = Arrays.copyOf(sequences, sequences.length); } /** * Create a new event handler group that combines the consumers in this group with otherHandlerGroup. * * @param otherHandlerGroup the event handler group to combine. * @return a new EventHandlerGroup combining the existing and new consumers into a single dependency group. */ public EventHandlerGroup and(final EventHandlerGroup otherHandlerGroup) { final Sequence[] combinedSequences = new Sequence[this.sequences.length + otherHandlerGroup.sequences.length]; System.arraycopy(this.sequences, 0, combinedSequences, 0, this.sequences.length); System.arraycopy( otherHandlerGroup.sequences, 0, combinedSequences, this.sequences.length, otherHandlerGroup.sequences.length); return new EventHandlerGroup<>(disruptor, consumerRepository, combinedSequences); } /** * Create a new event handler group that combines the handlers in this group with processors. * * @param processors the processors to combine. * @return a new EventHandlerGroup combining the existing and new processors into a single dependency group. */ public EventHandlerGroup and(final EventProcessor... processors) { Sequence[] combinedSequences = new Sequence[sequences.length + processors.length]; for (int i = 0; i < processors.length; i++) { consumerRepository.add(processors[i]); combinedSequences[i] = processors[i].getSequence(); } System.arraycopy(sequences, 0, combinedSequences, processors.length, sequences.length); return new EventHandlerGroup<>(disruptor, consumerRepository, combinedSequences); } /** *

Set up batch handlers to consume events from the ring buffer. These handlers will only process events * after every {@link EventProcessor} in this group has processed the event.

* *

This method is generally used as part of a chain. For example if the handler A must * process events before handler B:

* *
dw.handleEventsWith(A).then(B);
* * @param handlers the batch handlers that will process events. * @return a {@link EventHandlerGroup} that can be used to set up a event processor barrier over the created event processors. */ @SafeVarargs public final EventHandlerGroup then(final EventHandler... handlers) { return handleEventsWith(handlers); } /** *

Set up batch handlers to consume events from the ring buffer. These handlers will only process events * after every {@link EventProcessor} in this group has processed the event.

* *

This method is generally used as part of a chain. For example if the handler A must * process events before handler B:

* *
dw.handleEventsWith(A).then(B);
* * @param batchRewindStrategy a {@link BatchRewindStrategy} for customizing how to handle a {@link RewindableException}. * @param handlers the rewindable event handlers that will process events. * @return a {@link EventHandlerGroup} that can be used to set up a event processor barrier over the created event processors. */ @SafeVarargs public final EventHandlerGroup then(final BatchRewindStrategy batchRewindStrategy, final RewindableEventHandler... handlers) { return handleEventsWith(batchRewindStrategy, handlers); } /** *

Set up custom event processors to handle events from the ring buffer. The Disruptor will * automatically start these processors when {@link Disruptor#start()} is called.

* *

This method is generally used as part of a chain. For example if the handler A must * process events before handler B:

* * @param eventProcessorFactories the event processor factories to use to create the event processors that will process events. * @return a {@link EventHandlerGroup} that can be used to chain dependencies. */ @SafeVarargs public final EventHandlerGroup then(final EventProcessorFactory... eventProcessorFactories) { return handleEventsWith(eventProcessorFactories); } /** *

Set up batch handlers to handle events from the ring buffer. These handlers will only process events * after every {@link EventProcessor} in this group has processed the event.

* *

This method is generally used as part of a chain. For example if A must * process events before B:

* *
dw.after(A).handleEventsWith(B);
* * @param handlers the batch handlers that will process events. * @return a {@link EventHandlerGroup} that can be used to set up a event processor barrier over the created event processors. */ @SafeVarargs public final EventHandlerGroup handleEventsWith(final EventHandler... handlers) { return disruptor.createEventProcessors(sequences, handlers); } /** *

Set up batch handlers to handle events from the ring buffer. These handlers will only process events * after every {@link EventProcessor} in this group has processed the event.

* *

This method is generally used as part of a chain. For example if A must * process events before B:

* *
dw.after(A).handleEventsWith(B);
* * @param batchRewindStrategy a {@link BatchRewindStrategy} for customizing how to handle a {@link RewindableException}. * @param handlers the rewindable event handlers that will process events. * @return a {@link EventHandlerGroup} that can be used to set up a event processor barrier over the created event processors. */ @SafeVarargs public final EventHandlerGroup handleEventsWith(final BatchRewindStrategy batchRewindStrategy, final RewindableEventHandler... handlers) { return disruptor.createEventProcessors(sequences, batchRewindStrategy, handlers); } /** *

Set up custom event processors to handle events from the ring buffer. The Disruptor will * automatically start these processors when {@link Disruptor#start()} is called.

* *

This method is generally used as part of a chain. For example if A must * process events before B:

* *
dw.after(A).handleEventsWith(B);
* * @param eventProcessorFactories the event processor factories to use to create the event processors that will process events. * @return a {@link EventHandlerGroup} that can be used to chain dependencies. */ @SafeVarargs public final EventHandlerGroup handleEventsWith(final EventProcessorFactory... eventProcessorFactories) { return disruptor.createEventProcessors(sequences, eventProcessorFactories); } /** * Create a dependency barrier for the processors in this group. * This allows custom event processors to have dependencies on * {@link com.lmax.disruptor.BatchEventProcessor}s created by the disruptor. * * @return a {@link SequenceBarrier} including all the processors in this group. */ public SequenceBarrier asSequenceBarrier() { return disruptor.getRingBuffer().newBarrier(sequences); } } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/EventProcessorFactory.java ================================================ package com.lmax.disruptor.dsl; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.Sequence; /** * A factory interface to make it possible to include custom event processors in a chain: * *

 * disruptor.handleEventsWith(handler1).then((ringBuffer, barrierSequences) -> new CustomEventProcessor(ringBuffer, barrierSequences));
 * 
* * @param implementation storing the data for sharing during exchange or parallel coordination of an event. */ public interface EventProcessorFactory { /** * Create a new event processor that gates on barrierSequences. * * @param ringBuffer the ring buffer to receive events from. * @param barrierSequences the sequences to gate on * @return a new EventProcessor that gates on barrierSequences before processing events */ EventProcessor createEventProcessor(RingBuffer ringBuffer, Sequence[] barrierSequences); } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/EventProcessorInfo.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import java.util.concurrent.ThreadFactory; /** * Wrapper class to tie together a particular event processing stage

* *

Tracks the event processor instance, the event handler instance, and sequence barrier which the stage is attached to.

* */ class EventProcessorInfo implements ConsumerInfo { private final EventProcessor eventprocessor; private final SequenceBarrier barrier; private boolean endOfChain = true; EventProcessorInfo(final EventProcessor eventprocessor, final SequenceBarrier barrier) { this.eventprocessor = eventprocessor; this.barrier = barrier; } public EventProcessor getEventProcessor() { return eventprocessor; } @Override public Sequence[] getSequences() { return new Sequence[]{eventprocessor.getSequence()}; } @Override public SequenceBarrier getBarrier() { return barrier; } @Override public boolean isEndOfChain() { return endOfChain; } @Override public void start(final ThreadFactory threadFactory) { final Thread thread = threadFactory.newThread(eventprocessor); if (null == thread) { throw new RuntimeException("Failed to create thread to run: " + eventprocessor); } thread.start(); } @Override public void halt() { eventprocessor.halt(); } /** * */ @Override public void markAsUsedInBarrier() { endOfChain = false; } @Override public boolean isRunning() { return eventprocessor.isRunning(); } } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/ExceptionHandlerSetting.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.EventHandlerIdentity; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.ExceptionHandler; /** * A support class used as part of setting an exception handler for a specific event handler. * For example: *
disruptorWizard.handleExceptionsIn(eventHandler).with(exceptionHandler);
* * @param the type of event being handled. */ public class ExceptionHandlerSetting { private final EventHandlerIdentity handlerIdentity; private final ConsumerRepository consumerRepository; ExceptionHandlerSetting( final EventHandlerIdentity handlerIdentity, final ConsumerRepository consumerRepository) { this.handlerIdentity = handlerIdentity; this.consumerRepository = consumerRepository; } /** * Specify the {@link ExceptionHandler} to use with the event handler. * * @param exceptionHandler the exception handler to use. */ @SuppressWarnings("unchecked") public void with(final ExceptionHandler exceptionHandler) { final EventProcessor eventProcessor = consumerRepository.getEventProcessorFor(handlerIdentity); if (eventProcessor instanceof BatchEventProcessor) { ((BatchEventProcessor) eventProcessor).setExceptionHandler(exceptionHandler); consumerRepository.getBarrierFor(handlerIdentity).alert(); } else { throw new RuntimeException( "EventProcessor: " + eventProcessor + " is not a BatchEventProcessor " + "and does not support exception handlers"); } } } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/ExceptionHandlerWrapper.java ================================================ package com.lmax.disruptor.dsl; import com.lmax.disruptor.ExceptionHandler; import com.lmax.disruptor.ExceptionHandlers; /** * A mutable exception handler wrapper * @param The data type of the underlying {@link com.lmax.disruptor.RingBuffer} */ public class ExceptionHandlerWrapper implements ExceptionHandler { private ExceptionHandler delegate; /** * Switch to a different exception handler * @param exceptionHandler the exception handler to use from now on */ public void switchTo(final ExceptionHandler exceptionHandler) { this.delegate = exceptionHandler; } @Override public void handleEventException(final Throwable ex, final long sequence, final T event) { getExceptionHandler().handleEventException(ex, sequence, event); } @Override public void handleOnStartException(final Throwable ex) { getExceptionHandler().handleOnStartException(ex); } @Override public void handleOnShutdownException(final Throwable ex) { getExceptionHandler() .handleOnShutdownException(ex); } private ExceptionHandler getExceptionHandler() { ExceptionHandler handler = delegate; return handler == null ? ExceptionHandlers.defaultHandler() : handler; } } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/ProducerType.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl; /** * Defines producer types to support creation of RingBuffer with correct sequencer and publisher. */ public enum ProducerType { /** * Create a RingBuffer with a single event publisher to the RingBuffer */ SINGLE, /** * Create a RingBuffer supporting multiple event publishers to the one RingBuffer */ MULTI } ================================================ FILE: src/main/java/com/lmax/disruptor/dsl/package-info.java ================================================ /** * A DSL-style API for setting up the disruptor pattern around a ring buffer. * *

Example code

*
{@code
 * // Specify the size of the ring buffer, must be power of 2.
 *  int bufferSize = 1024;
 *
 *  // Construct the Disruptor
 *  Disruptor disruptor = new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);
 *
 *  // Connect the handler
 *  disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event));
 *
 *  // Start the Disruptor, starts all threads running
 *  disruptor.start();
 *
 *  // Get the ring buffer from the Disruptor to be used for publishing.
 *  RingBuffer ringBuffer = disruptor.getRingBuffer();
 *
 *  ByteBuffer bb = ByteBuffer.allocate(8);
 *  for (long l = 0; true; l++)
 *  {
 *      bb.putLong(0, l);
 *      ringBuffer.publishEvent((event, sequence, buffer) -> event.set(buffer.getLong(0)), bb);
 *      Thread.sleep(1000);
 *  }
 * }
*/ package com.lmax.disruptor.dsl; ================================================ FILE: src/main/java/com/lmax/disruptor/package-info.java ================================================ /** * The Disruptor is a concurrent programming framework for exchanging and coordinating work as a continuous series of events. * It can be used as an alternative to wiring processing stages together via queues. * The Disruptor design has the characteristics of generating significantly less garbage than queues and separates the * concurrency concerns so non-locking algorithms can be employed resulting in greater scalability and performance. * *

It works on the principle of having a number of stages that are each single threaded with local state and memory. * No global memory exists and all communication is achieved by passing messages/state via managed ring buffers. * *

Almost any graph or pipeline structure can be composed via one or more Disruptor patterns. * *

UniCast a series of items between 1 publisher and 1 EventProcessor.

* *
{@code
 *                                           track to prevent wrap
 *                                           +------------------+
 *                                           |                  |
 *                                           |                  v
 * +----+    +-----+            +----+    +====+    +====+   +-----+
 * | P1 |--->| EP1 |            | P1 |--->| RB |<---| SB |   | EP1 |
 * +----+    +-----+            +----+    +====+    +====+   +-----+
 *                                   claim      get    ^        |
 *                                                     |        |
 *                                                     +--------+
 *                                                       waitFor
 * }
* *

Sequence a series of messages from multiple publishers

*
{@code
 *                                          track to prevent wrap
 *                                          +--------------------+
 *                                          |                    |
 *                                          |                    v
 * +----+                       +----+    +====+    +====+    +-----+
 * | P1 |-------+               | P1 |--->| RB |<---| SB |    | EP1 |
 * +----+       |               +----+    +====+    +====+    +-----+
 *              v                           ^   get    ^         |
 * +----+    +-----+            +----+      |          |         |
 * | P2 |--->| EP1 |            | P2 |------+          +---------+
 * +----+    +-----+            +----+      |            waitFor
 *              ^                           |
 * +----+       |               +----+      |
 * | P3 |-------+               | P3 |------+
 * +----+                       +----+
 * }
* *

Pipeline a series of messages

*
{@code
 *                           +----+    +-----+    +-----+    +-----+
 *                           | P1 |--->| EP1 |--->| EP2 |--->| EP3 |
 *                           +----+    +-----+    +-----+    +-----+
 *
 *
 *
 *                           track to prevent wrap
 *              +----------------------------------------------------------------+
 *              |                                                                |
 *              |                                                                v
 * +----+    +====+    +=====+    +-----+    +=====+    +-----+    +=====+    +-----+
 * | P1 |--->| RB |    | SB1 |<---| EP1 |<---| SB2 |<---| EP2 |<---| SB3 |<---| EP3 |
 * +----+    +====+    +=====+    +-----+    +=====+    +-----+    +=====+    +-----+
 *      claim   ^  get    |   waitFor           |   waitFor           |  waitFor
 *              |         |                     |                     |
 *              +---------+---------------------+---------------------+
 * }
* *

Multicast a series of messages to multiple EventProcessors

*
{@code
 *           +-----+                                        track to prevent wrap
 *    +----->| EP1 |                        +--------------------+----------+----------+
 *    |      +-----+                        |                    |          |          |
 *    |                                     |                    v          v          v
 * +----+    +-----+            +----+    +====+    +====+    +-----+    +-----+    +-----+
 * | P1 |--->| EP2 |            | P1 |--->| RB |<---| SB |    | EP1 |    | EP2 |    | EP3 |
 * +----+    +-----+            +----+    +====+    +====+    +-----+    +-----+    +-----+
 *    |                              claim      get    ^         |          |          |
 *    |      +-----+                                   |         |          |          |
 *    +----->| EP3 |                                   +---------+----------+----------+
 *           +-----+                                                 waitFor
 * }
* *

Replicate a message then fold back the results

*
{@code
 *           +-----+                               track to prevent wrap
 *    +----->| EP1 |-----+                   +-------------------------------+
 *    |      +-----+     |                   |                               |
 *    |                  v                   |                               v
 * +----+             +-----+   +----+    +====+               +=====+    +-----+
 * | P1 |             | EP3 |   | P1 |--->| RB |<--------------| SB2 |<---| EP3 |
 * +----+             +-----+   +----+    +====+               +=====+    +-----+
 *    |                  ^           claim   ^  get               |   waitFor
 *    |      +-----+     |                   |                    |
 *    +----->| EP2 |-----+                +=====+    +-----+      |
 *           +-----+                      | SB1 |<---| EP1 |<-----+
 *                                        +=====+    +-----+      |
 *                                           ^                    |
 *                                           |       +-----+      |
 *                                           +-------| EP2 |<-----+
 *                                          waitFor  +-----+
 * }
* *

Code Example

*
{@code
 * // Event holder for data to be exchanged
 * public final class ValueEvent
 * {
 *     private long value;
 *
 *     public long getValue()
 *     {
 *         return value;
 *     }
 *
 *     public void setValue(final long value)
 *     {
 *         this.value = value;
 *     }
 *
 *     public final static EventFactory EVENT_FACTORY = new EventFactory()
 *     {
 *         public ValueEvent newInstance()
 *         {
 *             return new ValueEvent();
 *         }
 *     };
 * }
 *
 * // Callback handler which can be implemented by EventProcessors
 * final EventHandler eventHandler = new EventHandler()
 * {
 *     public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch)
 *         throws Exception
 *     {
 *         // process a new event as it becomes available.
 *     }
 * };
 *
 * RingBuffer ringBuffer =
 *     new RingBuffer(ValueEvent.EVENT_FACTORY,
 *                                new SingleThreadedClaimStrategy(BUFFER_SIZE),
 *                                new SleepingWaitStrategy());
 *
 * SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();
 * BatchEventProcessor batchProcessor = new BatchEventProcessor(sequenceBarrier, eventHandler);
 * ringBuffer.setGatingSequences(batchProcessor.getSequence());
 *
 * // Each processor runs on a separate thread
 * EXECUTOR.submit(batchProcessor);
 *
 * // Publishers claim events in sequence
 * long sequence = ringBuffer.next();
 * ValueEvent event = ringBuffer.get(sequence);
 *
 * event.setValue(1234);
 *
 * // publish the event so it is available to EventProcessors
 * ringBuffer.publish(sequence);
 * }
*/ package com.lmax.disruptor; ================================================ FILE: src/main/java/com/lmax/disruptor/util/DaemonThreadFactory.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.util; import java.util.concurrent.ThreadFactory; /** * Access to a ThreadFactory instance. All threads are created with setDaemon(true). */ public enum DaemonThreadFactory implements ThreadFactory { /** * The singleton instance */ INSTANCE; @Override public Thread newThread(final Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } } ================================================ FILE: src/main/java/com/lmax/disruptor/util/ThreadHints.java ================================================ /* Copyright 2016 Gil Tene * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.util; /** * This class captures possible hints that may be used by some * runtimes to improve code performance. It is intended to capture hinting * behaviours that are implemented in or anticipated to be spec'ed under the * {@link java.lang.Thread} class in some Java SE versions, but missing in prior * versions. * @deprecated Use Thread.onSpinWait() directly. This class previously existed to accommodate * Java versions which do not have Thread.onSpinWait(). */ @Deprecated public final class ThreadHints { private ThreadHints() { } /** * Indicates that the caller is momentarily unable to progress, until the * occurrence of one or more actions on the part of other activities. By * invoking this method within each iteration of a spin-wait loop construct, * the calling thread indicates to the runtime that it is busy-waiting. The runtime * may take action to improve the performance of invoking spin-wait loop constructions. * * @deprecated Use Thread.onSpinWait() directly. This method previously existed to accommodate * Java versions which do not have Thread.onSpinWait(). */ @Deprecated public static void onSpinWait() { Thread.onSpinWait(); } } ================================================ FILE: src/main/java/com/lmax/disruptor/util/Util.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.util; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.Sequence; /** * Set of common functions used by the Disruptor. */ public final class Util { private static final int ONE_MILLISECOND_IN_NANOSECONDS = 1_000_000; /** * Calculate the next power of 2, greater than or equal to x. * *

From Hacker's Delight, Chapter 3, Harry S. Warren Jr. * * @param x Value to round up * @return The next power of 2 from x inclusive */ public static int ceilingNextPowerOfTwo(final int x) { return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1)); } /** * Get the minimum sequence from an array of {@link com.lmax.disruptor.Sequence}s. * * @param sequences to compare. * @return the minimum sequence found or Long.MAX_VALUE if the array is empty. */ public static long getMinimumSequence(final Sequence[] sequences) { return getMinimumSequence(sequences, Long.MAX_VALUE); } /** * Get the minimum sequence from an array of {@link com.lmax.disruptor.Sequence}s. * * @param sequences to compare. * @param minimum an initial default minimum. If the array is empty this value will be * returned. * @return the smaller of minimum sequence value found in {@code sequences} and {@code minimum}; * {@code minimum} if {@code sequences} is empty */ public static long getMinimumSequence(final Sequence[] sequences, final long minimum) { long minimumSequence = minimum; for (int i = 0, n = sequences.length; i < n; i++) { long value = sequences[i].get(); minimumSequence = Math.min(minimumSequence, value); } return minimumSequence; } /** * Get an array of {@link Sequence}s for the passed {@link EventProcessor}s. * * @param processors for which to get the sequences * @return the array of {@link Sequence}s */ public static Sequence[] getSequencesFor(final EventProcessor... processors) { Sequence[] sequences = new Sequence[processors.length]; for (int i = 0; i < sequences.length; i++) { sequences[i] = processors[i].getSequence(); } return sequences; } /** * Calculate the log base 2 of the supplied integer, essentially reports the location * of the highest bit. * * @param value Positive value to calculate log2 for. * @return The log2 value */ public static int log2(final int value) { if (value < 1) { throw new IllegalArgumentException("value must be a positive number"); } return Integer.SIZE - Integer.numberOfLeadingZeros(value) - 1; } /** * @param mutex The object to wait on * @param timeoutNanos The number of nanoseconds to wait for * @return the number of nanoseconds waited (approximately) * @throws InterruptedException if the underlying call to wait is interrupted */ public static long awaitNanos(final Object mutex, final long timeoutNanos) throws InterruptedException { long millis = timeoutNanos / ONE_MILLISECOND_IN_NANOSECONDS; long nanos = timeoutNanos % ONE_MILLISECOND_IN_NANOSECONDS; long t0 = System.nanoTime(); mutex.wait(millis, (int) nanos); long t1 = System.nanoTime(); return timeoutNanos - (t1 - t0); } } ================================================ FILE: src/main/java/com/lmax/disruptor/util/package-info.java ================================================ /** * Utility classes */ package com.lmax.disruptor.util; ================================================ FILE: src/main/java/module-info.java ================================================ module com.lmax.disruptor { exports com.lmax.disruptor; exports com.lmax.disruptor.dsl; exports com.lmax.disruptor.util; } ================================================ FILE: src/perftest/java/com/lmax/disruptor/AbstractPerfTestDisruptor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; public abstract class AbstractPerfTestDisruptor { public static final int RUNS = 7; protected void testImplementations() throws Exception { final int availableProcessors = Runtime.getRuntime().availableProcessors(); if (getRequiredProcessorCount() > availableProcessors) { System.out.print("*** Warning ***: your system has insufficient processors to execute the test efficiently. "); System.out.println("Processors required = " + getRequiredProcessorCount() + " available = " + availableProcessors); } PerfTestContext[] contexts = new PerfTestContext[RUNS]; System.out.println("Starting Disruptor tests"); for (int i = 0; i < RUNS; i++) { System.gc(); PerfTestContext context = runDisruptorPass(); contexts[i] = context; System.out.format("Run %d, Disruptor=%,d ops/sec BatchPercent=%.2f%% AverageBatchSize=%,d\n", i, context.getDisruptorOps(), context.getBatchPercent() * 100, (long) context.getAverageBatchSize()); } } public static void printResults(final String className, final PerfTestContext[] contexts, final long[] queueOps) { for (int i = 0; i < RUNS; i++) { PerfTestContext context = contexts[i]; System.out.format("%s run %d: BlockingQueue=%,d Disruptor=%,d ops/sec BatchPercent=%,d AverageBatchSize=%,d\n", className, Integer.valueOf(i), Long.valueOf(queueOps[i]), Long.valueOf(context.getDisruptorOps()), Double.valueOf(context.getBatchPercent()), Double.valueOf(context.getAverageBatchSize())); } } protected abstract int getRequiredProcessorCount(); protected abstract PerfTestContext runDisruptorPass() throws Exception; } ================================================ FILE: src/perftest/java/com/lmax/disruptor/AbstractPerfTestQueue.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; public abstract class AbstractPerfTestQueue { public static final int RUNS = 7; protected void testImplementations() throws Exception { final int availableProcessors = Runtime.getRuntime().availableProcessors(); if (getRequiredProcessorCount() > availableProcessors) { System.out.print( "*** Warning ***: your system has insufficient processors to execute the test efficiently. "); System.out.println( "Processors required = " + getRequiredProcessorCount() + " available = " + availableProcessors); } long[] queueOps = new long[RUNS]; System.out.println("Starting Queue tests"); for (int i = 0; i < RUNS; i++) { System.gc(); queueOps[i] = runQueuePass(); System.out.format("Run %d, BlockingQueue=%,d ops/sec%n", i, Long.valueOf(queueOps[i])); } } public static void printResults(final String className, final long[] disruptorOps, final long[] queueOps) { for (int i = 0; i < RUNS; i++) { System.out.format( "%s run %d: BlockingQueue=%,d Disruptor=%,d ops/sec\n", className, Integer.valueOf(i), Long.valueOf(queueOps[i]), Long.valueOf(disruptorOps[i])); } } protected abstract int getRequiredProcessorCount(); protected abstract long runQueuePass() throws Exception; } ================================================ FILE: src/perftest/java/com/lmax/disruptor/PerfTestContext.java ================================================ package com.lmax.disruptor; public class PerfTestContext { private long disruptorOps; private long batchesProcessedCount; private long iterations; public PerfTestContext() { } public long getDisruptorOps() { return disruptorOps; } public void setDisruptorOps(final long disruptorOps) { this.disruptorOps = disruptorOps; } public long getBatchesProcessedCount() { return batchesProcessedCount; } public double getBatchPercent() { if (batchesProcessedCount == 0) { return 0; } return 1 - (double) batchesProcessedCount / iterations; } public double getAverageBatchSize() { if (batchesProcessedCount == 0) { return -1; } return (double) iterations / batchesProcessedCount; } public void setBatchData(final long batchesProcessedCount, final long iterations) { this.batchesProcessedCount = batchesProcessedCount; this.iterations = iterations; } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/Constants.java ================================================ package com.lmax.disruptor.immutable; public class Constants { public static final long ITERATIONS = 1000 * 1000 * 100L; public static final int SIZE = 1 << 20; } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/CustomPerformanceTest.java ================================================ package com.lmax.disruptor.immutable; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.SingleProducerSequencer; import com.lmax.disruptor.YieldingWaitStrategy; import java.util.concurrent.locks.LockSupport; public class CustomPerformanceTest { private final CustomRingBuffer ringBuffer; public CustomPerformanceTest() { ringBuffer = new CustomRingBuffer<>(new SingleProducerSequencer(Constants.SIZE, new YieldingWaitStrategy())); } public void run() { try { doRun(); } catch (InterruptedException e) { e.printStackTrace(); } } private void doRun() throws InterruptedException { BatchEventProcessor batchEventProcessor = ringBuffer.createHandler(new SimpleEventHandler()); Thread t = new Thread(batchEventProcessor); t.start(); long iterations = Constants.ITERATIONS; for (long l = 0; l < iterations; l++) { SimpleEvent e = new SimpleEvent(l, l, l, l); ringBuffer.put(e); } while (batchEventProcessor.getSequence().get() != iterations - 1) { LockSupport.parkNanos(1); } batchEventProcessor.halt(); t.join(); } public static void main(final String[] args) { new CustomPerformanceTest().run(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/CustomRingBuffer.java ================================================ package com.lmax.disruptor.immutable; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.DataProvider; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.Sequencer; public class CustomRingBuffer implements DataProvider>, EventAccessor { private static final class AccessorEventHandler implements EventHandler> { private final EventHandler handler; private AccessorEventHandler(final EventHandler handler) { this.handler = handler; } @Override public void onEvent(final EventAccessor accessor, final long sequence, final boolean endOfBatch) throws Exception { this.handler.onEvent(accessor.take(sequence), sequence, endOfBatch); } @Override public void onShutdown() { handler.onShutdown(); } @Override public void onStart() { handler.onStart(); } } private final Sequencer sequencer; private final Object[] buffer; private final int mask; public CustomRingBuffer(final Sequencer sequencer) { this.sequencer = sequencer; buffer = new Object[sequencer.getBufferSize()]; mask = sequencer.getBufferSize() - 1; } private int index(final long sequence) { return (int) sequence & mask; } public void put(final T e) { long next = sequencer.next(); buffer[index(next)] = e; sequencer.publish(next); } @SuppressWarnings("unchecked") @Override public T take(final long sequence) { int index = index(sequence); T t = (T) buffer[index]; buffer[index] = null; return t; } @Override public EventAccessor get(final long sequence) { return this; } public BatchEventProcessor> createHandler(final EventHandler handler) { BatchEventProcessor> processor = new BatchEventProcessorBuilder().build( this, sequencer.newBarrier(), new AccessorEventHandler<>(handler)); sequencer.addGatingSequences(processor.getSequence()); return processor; } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/EventAccessor.java ================================================ package com.lmax.disruptor.immutable; public interface EventAccessor { T take(long sequence); } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/EventHolder.java ================================================ package com.lmax.disruptor.immutable; import com.lmax.disruptor.EventFactory; public class EventHolder { public static final EventFactory FACTORY = EventHolder::new; public SimpleEvent event; } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/EventHolderHandler.java ================================================ package com.lmax.disruptor.immutable; import com.lmax.disruptor.EventHandler; public class EventHolderHandler implements EventHandler { private final EventHandler delegate; public EventHolderHandler(final EventHandler delegate) { this.delegate = delegate; } @Override public void onEvent(final EventHolder holder, final long sequence, final boolean endOfBatch) throws Exception { delegate.onEvent(holder.event, sequence, endOfBatch); holder.event = null; } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/SimpleEvent.java ================================================ package com.lmax.disruptor.immutable; public class SimpleEvent { private final long id; private final long v1; private final long v2; private final long v3; public SimpleEvent(final long id, final long v1, final long v2, final long v3) { this.id = id; this.v1 = v1; this.v2 = v2; this.v3 = v3; } public long getCounter() { return v1; } @Override public String toString() { return "SimpleEvent [id=" + id + ", v1=" + v1 + ", v2=" + v2 + ", v3=" + v3 + "]"; } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/SimpleEventHandler.java ================================================ package com.lmax.disruptor.immutable; import com.lmax.disruptor.EventHandler; public class SimpleEventHandler implements EventHandler { public long counter; @Override public void onEvent(final SimpleEvent arg0, final long arg1, final boolean arg2) throws Exception { counter += arg0.getCounter(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/immutable/SimplePerformanceTest.java ================================================ package com.lmax.disruptor.immutable; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.EventTranslatorOneArg; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.YieldingWaitStrategy; import java.util.concurrent.locks.LockSupport; public class SimplePerformanceTest { private final RingBuffer ringBuffer; private final EventHolderHandler eventHolderHandler; public SimplePerformanceTest() { ringBuffer = RingBuffer.createSingleProducer(EventHolder.FACTORY, Constants.SIZE, new YieldingWaitStrategy()); eventHolderHandler = new EventHolderHandler(new SimpleEventHandler()); } public void run() { try { doRun(); } catch (InterruptedException e) { e.printStackTrace(); } } private void doRun() throws InterruptedException { BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build( ringBuffer, ringBuffer.newBarrier(), eventHolderHandler); ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); Thread t = new Thread(batchEventProcessor); t.start(); long iterations = Constants.ITERATIONS; for (long l = 0; l < iterations; l++) { SimpleEvent e = new SimpleEvent(l, l, l, l); ringBuffer.publishEvent(TRANSLATOR, e); } while (batchEventProcessor.getSequence().get() != iterations - 1) { LockSupport.parkNanos(1); } batchEventProcessor.halt(); t.join(); } private static final EventTranslatorOneArg TRANSLATOR = (holder, arg1, event) -> holder.event = event; public static void main(final String[] args) { new SimplePerformanceTest().run(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/offheap/OneToOneOffHeapThroughputTest.java ================================================ package com.lmax.disruptor.offheap; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.DataProvider; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.SingleProducerSequencer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.util.DaemonThreadFactory; import com.lmax.disruptor.util.PaddedLong; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.locks.LockSupport; public class OneToOneOffHeapThroughputTest extends AbstractPerfTestDisruptor { private static final int BLOCK_SIZE = 256; private static final int BUFFER_SIZE = 1024 * 1024; private static final long ITERATIONS = 1000 * 1000 * 10L; private final Executor executor = Executors.newFixedThreadPool(1, DaemonThreadFactory.INSTANCE); private final WaitStrategy waitStrategy = new YieldingWaitStrategy(); private final OffHeapRingBuffer buffer = new OffHeapRingBuffer(new SingleProducerSequencer(BUFFER_SIZE, waitStrategy), BLOCK_SIZE); private final ByteBufferHandler handler = new ByteBufferHandler(); private final BatchEventProcessor processor = new BatchEventProcessorBuilder().build(buffer, buffer.newBarrier(), handler); { buffer.addGatingSequences(processor.getSequence()); } private final Random r = new Random(1); private final byte[] data = new byte[BLOCK_SIZE]; public OneToOneOffHeapThroughputTest() { r.nextBytes(data); } @Override protected int getRequiredProcessorCount() { return 2; } @Override protected PerfTestContext runDisruptorPass() throws Exception { PerfTestContext perfTestContext = new PerfTestContext(); byte[] data = this.data; final CountDownLatch latch = new CountDownLatch(1); long expectedCount = processor.getSequence().get() + ITERATIONS; handler.reset(latch, ITERATIONS); executor.execute(processor); long start = System.currentTimeMillis(); final OffHeapRingBuffer rb = buffer; for (long i = 0; i < ITERATIONS; i++) { rb.put(data); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS); waitForEventProcessorSequence(expectedCount); processor.halt(); return perfTestContext; } private void waitForEventProcessorSequence(final long expectedCount) { while (processor.getSequence().get() < expectedCount) { LockSupport.parkNanos(1); } } public static void main(final String[] args) throws Exception { new OneToOneOffHeapThroughputTest().testImplementations(); } public static class ByteBufferHandler implements EventHandler { private final PaddedLong total = new PaddedLong(); private final PaddedLong batchesProcessed = new PaddedLong(); private long expectedCount; private CountDownLatch latch; @Override public void onEvent(final ByteBuffer event, final long sequence, final boolean endOfBatch) throws Exception { final int start = event.position(); for (int i = start, size = start + BLOCK_SIZE; i < size; i += 8) { total.set(total.get() + event.getLong(i)); } if (--expectedCount == 0) { latch.countDown(); } } public long getTotal() { return total.get(); } public long getBatchesProcessed() { return batchesProcessed.get(); } public void reset(final CountDownLatch latch, final long expectedCount) { this.latch = latch; this.expectedCount = expectedCount; this.total.set(0); this.batchesProcessed.set(0); } @Override public void onBatchStart(final long batchSize, final long queueDepth) { batchesProcessed.increment(); } } public static class OffHeapRingBuffer implements DataProvider { private final Sequencer sequencer; private final int entrySize; private final ByteBuffer buffer; private final int mask; private final ThreadLocal perThreadBuffer = new ThreadLocal<>() { @Override protected ByteBuffer initialValue() { return buffer.duplicate().order(ByteOrder.nativeOrder()); } }; public OffHeapRingBuffer(final Sequencer sequencer, final int entrySize) { this.sequencer = sequencer; this.entrySize = entrySize; this.mask = sequencer.getBufferSize() - 1; buffer = ByteBuffer.allocateDirect(sequencer.getBufferSize() * entrySize).order(ByteOrder.nativeOrder()); } public void addGatingSequences(final Sequence sequence) { sequencer.addGatingSequences(sequence); } public SequenceBarrier newBarrier() { return sequencer.newBarrier(); } @Override public ByteBuffer get(final long sequence) { int index = index(sequence); int position = index * entrySize; int limit = position + entrySize; ByteBuffer byteBuffer = perThreadBuffer.get(); byteBuffer.position(position).limit(limit); return byteBuffer; } public void put(final byte[] data) { long next = sequencer.next(); try { get(next).put(data); } finally { sequencer.publish(next); } } private int index(final long next) { return (int) (next & mask); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/offheap/OneToOneOnHeapThroughputTest.java ================================================ package com.lmax.disruptor.offheap; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.util.DaemonThreadFactory; import com.lmax.disruptor.util.PaddedLong; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.locks.LockSupport; public class OneToOneOnHeapThroughputTest extends AbstractPerfTestDisruptor { private static final int BLOCK_SIZE = 256; private static final int BUFFER_SIZE = 1024 * 1024; private static final long ITERATIONS = 1000 * 1000 * 10L; private static final boolean SLICED_BUFFER = Boolean.getBoolean("sliced"); private final Executor executor = Executors.newFixedThreadPool(1, DaemonThreadFactory.INSTANCE); private final WaitStrategy waitStrategy = new YieldingWaitStrategy(); private final RingBuffer buffer = RingBuffer.createSingleProducer( SLICED_BUFFER ? SlicedBufferFactory.direct(BLOCK_SIZE, BUFFER_SIZE) : BufferFactory.direct(BLOCK_SIZE), BUFFER_SIZE, waitStrategy); private final ByteBufferHandler handler = new ByteBufferHandler(); private final BatchEventProcessor processor = new BatchEventProcessorBuilder().build(buffer, buffer.newBarrier(), handler); { buffer.addGatingSequences(processor.getSequence()); } private final Random r = new Random(1); private final byte[] data = new byte[BLOCK_SIZE]; public OneToOneOnHeapThroughputTest() { r.nextBytes(data); } @Override protected int getRequiredProcessorCount() { return 2; } @Override protected PerfTestContext runDisruptorPass() throws Exception { PerfTestContext perfTestContext = new PerfTestContext(); byte[] data = this.data; final CountDownLatch latch = new CountDownLatch(1); long expectedCount = processor.getSequence().get() + ITERATIONS; handler.reset(latch, ITERATIONS); executor.execute(processor); long start = System.currentTimeMillis(); final RingBuffer rb = buffer; for (long i = 0; i < ITERATIONS; i++) { long next = rb.next(); ByteBuffer event = rb.get(next); event.clear(); event.put(data); event.flip(); rb.publish(next); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS); waitForEventProcessorSequence(expectedCount); processor.halt(); return perfTestContext; } private void waitForEventProcessorSequence(final long expectedCount) { while (processor.getSequence().get() < expectedCount) { LockSupport.parkNanos(1); } } public static void main(final String[] args) throws Exception { new OneToOneOnHeapThroughputTest().testImplementations(); } public static class ByteBufferHandler implements EventHandler { private final PaddedLong total = new PaddedLong(); private final PaddedLong batchesProcessed = new PaddedLong(); private long expectedCount; private CountDownLatch latch; @Override public void onEvent(final ByteBuffer event, final long sequence, final boolean endOfBatch) throws Exception { for (int i = 0; i < BLOCK_SIZE; i += 8) { total.set(total.get() + event.getLong(i)); } if (--expectedCount == 0) { latch.countDown(); } } public long getTotal() { return total.get(); } public long getBatchesProcessed() { return batchesProcessed.get(); } public void reset(final CountDownLatch latch, final long expectedCount) { this.latch = latch; this.expectedCount = expectedCount; this.total.set(0); this.batchesProcessed.set(0); } @Override public void onBatchStart(final long batchSize, final long queueDepth) { batchesProcessed.increment(); } } private static final class BufferFactory implements EventFactory { private final boolean isDirect; private final int size; private BufferFactory(final boolean isDirect, final int size) { this.isDirect = isDirect; this.size = size; } @Override public ByteBuffer newInstance() { if (isDirect) { return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); } else { return ByteBuffer.allocate(size).order(ByteOrder.nativeOrder()); } } public static BufferFactory direct(final int size) { return new BufferFactory(true, size); } @SuppressWarnings("unused") public static BufferFactory heap(final int size) { return new BufferFactory(false, size); } } private static final class SlicedBufferFactory implements EventFactory { private final boolean isDirect; private final int size; private final int total; private ByteBuffer buffer; private SlicedBufferFactory(final boolean isDirect, final int size, final int total) { this.isDirect = isDirect; this.size = size; this.total = total; this.buffer = (isDirect ? ByteBuffer.allocateDirect(size * total) : ByteBuffer.allocate(size * total)) .order(ByteOrder.nativeOrder()); this.buffer.limit(0); } @Override public ByteBuffer newInstance() { if (this.buffer.limit() == this.buffer.capacity()) { this.buffer = (isDirect ? ByteBuffer.allocateDirect(size * total) : ByteBuffer.allocate(size * total)) .order(ByteOrder.nativeOrder()); this.buffer.limit(0); } final int limit = this.buffer.limit(); this.buffer.limit(limit + size); this.buffer.position(limit); final ByteBuffer slice = this.buffer.slice().order(ByteOrder.nativeOrder()); return slice; } public static SlicedBufferFactory direct(final int size, final int total) { return new SlicedBufferFactory(true, size, total); } @SuppressWarnings("unused") public static SlicedBufferFactory heap(final int size, final int total) { return new SlicedBufferFactory(false, size, total); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/queue/OneToOneQueueBatchedThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.queue; import com.lmax.disruptor.AbstractPerfTestQueue; import com.lmax.disruptor.support.ValueAdditionBatchQueueProcessor; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import static com.lmax.disruptor.support.PerfTestUtil.failIf; /** *

 * UniCast a series of items between 1 publisher and 1 event processor.
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 * Queue Based:
 * ============
 *
 *        put      take
 * +----+    +====+    +-----+
 * | P1 |--->| Q1 |<---| EP1 |
 * +----+    +====+    +-----+
 *
 * P1  - Publisher 1
 * Q1  - Queue 1
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneQueueBatchedThroughputTest extends AbstractPerfTestQueue { private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 10L; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); private final long expectedResult = ITERATIONS * 3L; /////////////////////////////////////////////////////////////////////////////////////////////// private final BlockingQueue blockingQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final ValueAdditionBatchQueueProcessor queueProcessor = new ValueAdditionBatchQueueProcessor(blockingQueue, ITERATIONS); /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } @Override protected long runQueuePass() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); queueProcessor.reset(latch); Future future = executor.submit(queueProcessor); long start = System.currentTimeMillis(); for (long i = 0; i < ITERATIONS; i++) { blockingQueue.put(3L); } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); queueProcessor.halt(); future.cancel(true); failIf(expectedResult, 0); return opsPerSecond; } public static void main(final String[] args) throws Exception { OneToOneQueueBatchedThroughputTest test = new OneToOneQueueBatchedThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/queue/OneToOneQueueThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.queue; import com.lmax.disruptor.AbstractPerfTestQueue; import com.lmax.disruptor.support.ValueAdditionQueueProcessor; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import static com.lmax.disruptor.support.PerfTestUtil.failIf; /** *
 * UniCast a series of items between 1 publisher and 1 event processor.
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 * Queue Based:
 * ============
 *
 *        put      take
 * +----+    +====+    +-----+
 * | P1 |--->| Q1 |<---| EP1 |
 * +----+    +====+    +-----+
 *
 * P1  - Publisher 1
 * Q1  - Queue 1
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneQueueThroughputTest extends AbstractPerfTestQueue { private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 10L; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); private final long expectedResult = ITERATIONS * 3L; /////////////////////////////////////////////////////////////////////////////////////////////// private final BlockingQueue blockingQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final ValueAdditionQueueProcessor queueProcessor = new ValueAdditionQueueProcessor(blockingQueue, ITERATIONS - 1); /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } @Override protected long runQueuePass() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); queueProcessor.reset(latch); Future future = executor.submit(queueProcessor); long start = System.currentTimeMillis(); for (long i = 0; i < ITERATIONS; i++) { blockingQueue.put(3L); } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); queueProcessor.halt(); future.cancel(true); failIf(expectedResult, 0); return opsPerSecond; } public static void main(final String[] args) throws Exception { OneToOneQueueThroughputTest test = new OneToOneQueueThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/queue/OneToThreeDiamondQueueThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.queue; import com.lmax.disruptor.AbstractPerfTestQueue; import com.lmax.disruptor.support.FizzBuzzQueueProcessor; import com.lmax.disruptor.support.FizzBuzzStep; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import static com.lmax.disruptor.support.PerfTestUtil.failIf; /** *
 * Produce an event replicated to two event processors and fold back to a single third event processor.
 *
 *           +-----+
 *    +----->| EP1 |------+
 *    |      +-----+      |
 *    |                   v
 * +----+              +-----+
 * | P1 |              | EP3 |
 * +----+              +-----+
 *    |                   ^
 *    |      +-----+      |
 *    +----->| EP2 |------+
 *           +-----+
 *
 *
 * Queue Based:
 * ============
 *                 take       put
 *     put   +====+    +-----+    +====+  take
 *    +----->| Q1 |<---| EP1 |--->| Q3 |<------+
 *    |      +====+    +-----+    +====+       |
 *    |                                        |
 * +----+    +====+    +-----+    +====+    +-----+
 * | P1 |--->| Q2 |<---| EP2 |--->| Q4 |<---| EP3 |
 * +----+    +====+    +-----+    +====+    +-----+
 *
 * P1  - Publisher 1
 * Q1  - Queue 1
 * Q2  - Queue 2
 * Q3  - Queue 3
 * Q4  - Queue 4
 * EP1 - EventProcessor 1
 * EP2 - EventProcessor 2
 * EP3 - EventProcessor 3
 *
 *
 * Disruptor:
 * ==========
 *                    track to prevent wrap
 *              +-------------------------------+
 *              |                               |
 *              |                               v
 * +----+    +====+               +=====+    +-----+
 * | P1 |--->| RB |<--------------| SB2 |<---| EP3 |
 * +----+    +====+               +=====+    +-----+
 *      claim   ^  get               |   waitFor
 *              |                    |
 *           +=====+    +-----+      |
 *           | SB1 |<---| EP1 |<-----+
 *           +=====+    +-----+      |
 *              ^                    |
 *              |       +-----+      |
 *              +-------| EP2 |<-----+
 *             waitFor  +-----+
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB1 - SequenceBarrier 1
 * EP1 - EventProcessor 1
 * EP2 - EventProcessor 2
 * SB2 - SequenceBarrier 2
 * EP3 - EventProcessor 3
 *
 * 
*/ public final class OneToThreeDiamondQueueThroughputTest extends AbstractPerfTestQueue { private static final int NUM_EVENT_PROCESSORS = 3; private static final int BUFFER_SIZE = 1024 * 8; private static final long ITERATIONS = 1000L * 1000L * 100L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_EVENT_PROCESSORS, DaemonThreadFactory.INSTANCE); private final long expectedResult; { long temp = 0L; for (long i = 0; i < ITERATIONS; i++) { boolean fizz = 0 == (i % 3L); boolean buzz = 0 == (i % 5L); if (fizz && buzz) { ++temp; } } expectedResult = temp; } /////////////////////////////////////////////////////////////////////////////////////////////// private final BlockingQueue fizzInputQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final BlockingQueue buzzInputQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final BlockingQueue fizzOutputQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final BlockingQueue buzzOutputQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final FizzBuzzQueueProcessor fizzQueueProcessor = new FizzBuzzQueueProcessor(FizzBuzzStep.FIZZ, fizzInputQueue, buzzInputQueue, fizzOutputQueue, buzzOutputQueue, ITERATIONS - 1); private final FizzBuzzQueueProcessor buzzQueueProcessor = new FizzBuzzQueueProcessor(FizzBuzzStep.BUZZ, fizzInputQueue, buzzInputQueue, fizzOutputQueue, buzzOutputQueue, ITERATIONS - 1); private final FizzBuzzQueueProcessor fizzBuzzQueueProcessor = new FizzBuzzQueueProcessor(FizzBuzzStep.FIZZ_BUZZ, fizzInputQueue, buzzInputQueue, fizzOutputQueue, buzzOutputQueue, ITERATIONS - 1); /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected long runQueuePass() throws Exception { final CountDownLatch latch = new CountDownLatch(1); fizzBuzzQueueProcessor.reset(latch); Future[] futures = new Future[NUM_EVENT_PROCESSORS]; futures[0] = executor.submit(fizzQueueProcessor); futures[1] = executor.submit(buzzQueueProcessor); futures[2] = executor.submit(fizzBuzzQueueProcessor); long start = System.currentTimeMillis(); for (long i = 0; i < ITERATIONS; i++) { Long value = Long.valueOf(i); fizzInputQueue.put(value); buzzInputQueue.put(value); } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); fizzQueueProcessor.halt(); buzzQueueProcessor.halt(); fizzBuzzQueueProcessor.halt(); for (Future future : futures) { future.cancel(true); } failIf(expectedResult, 0); return opsPerSecond; } public static void main(final String[] args) throws Exception { new OneToThreeDiamondQueueThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/queue/OneToThreePipelineQueueThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.queue; import com.lmax.disruptor.AbstractPerfTestQueue; import com.lmax.disruptor.support.FunctionQueueProcessor; import com.lmax.disruptor.support.FunctionStep; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import static com.lmax.disruptor.support.PerfTestUtil.failIf; /** *
 *
 * Pipeline a series of stages from a publisher to ultimate event processor.
 * Each event processor depends on the output of the event processor.
 *
 * +----+    +-----+    +-----+    +-----+
 * | P1 |--->| EP1 |--->| EP2 |--->| EP3 |
 * +----+    +-----+    +-----+    +-----+
 *
 *
 * Queue Based:
 * ============
 *
 *        put      take        put      take        put      take
 * +----+    +====+    +-----+    +====+    +-----+    +====+    +-----+
 * | P1 |--->| Q1 |<---| EP1 |--->| Q2 |<---| EP2 |--->| Q3 |<---| EP3 |
 * +----+    +====+    +-----+    +====+    +-----+    +====+    +-----+
 *
 * P1  - Publisher 1
 * Q1  - Queue 1
 * EP1 - EventProcessor 1
 * Q2  - Queue 2
 * EP2 - EventProcessor 2
 * Q3  - Queue 3
 * EP3 - EventProcessor 3
 *
 * 
*/ public final class OneToThreePipelineQueueThroughputTest extends AbstractPerfTestQueue { private static final int NUM_EVENT_PROCESSORS = 3; private static final int BUFFER_SIZE = 1024 * 8; private static final long ITERATIONS = 1000L * 1000L * 10L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_EVENT_PROCESSORS, DaemonThreadFactory.INSTANCE); private static final long OPERAND_TWO_INITIAL_VALUE = 777L; private final long expectedResult; { long temp = 0L; long operandTwo = OPERAND_TWO_INITIAL_VALUE; for (long i = 0; i < ITERATIONS; i++) { long stepOneResult = i + operandTwo--; long stepTwoResult = stepOneResult + 3; if ((stepTwoResult & 4L) == 4L) { ++temp; } } expectedResult = temp; } /////////////////////////////////////////////////////////////////////////////////////////////// private final BlockingQueue stepOneQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final BlockingQueue stepTwoQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final BlockingQueue stepThreeQueue = new LinkedBlockingQueue<>(BUFFER_SIZE); private final FunctionQueueProcessor stepOneQueueProcessor = new FunctionQueueProcessor(FunctionStep.ONE, stepOneQueue, stepTwoQueue, stepThreeQueue, ITERATIONS - 1); private final FunctionQueueProcessor stepTwoQueueProcessor = new FunctionQueueProcessor(FunctionStep.TWO, stepOneQueue, stepTwoQueue, stepThreeQueue, ITERATIONS - 1); private final FunctionQueueProcessor stepThreeQueueProcessor = new FunctionQueueProcessor(FunctionStep.THREE, stepOneQueue, stepTwoQueue, stepThreeQueue, ITERATIONS - 1); /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected long runQueuePass() throws Exception { CountDownLatch latch = new CountDownLatch(1); stepThreeQueueProcessor.reset(latch); Future[] futures = new Future[NUM_EVENT_PROCESSORS]; futures[0] = executor.submit(stepOneQueueProcessor); futures[1] = executor.submit(stepTwoQueueProcessor); futures[2] = executor.submit(stepThreeQueueProcessor); long start = System.currentTimeMillis(); long operandTwo = OPERAND_TWO_INITIAL_VALUE; for (long i = 0; i < ITERATIONS; i++) { long[] values = new long[2]; values[0] = i; values[1] = operandTwo--; stepOneQueue.put(values); } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); stepOneQueueProcessor.halt(); stepTwoQueueProcessor.halt(); stepThreeQueueProcessor.halt(); for (Future future : futures) { future.cancel(true); } failIf(expectedResult, 0); return opsPerSecond; } public static void main(final String[] args) throws Exception { new OneToThreePipelineQueueThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/queue/OneToThreeQueueThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.queue; import com.lmax.disruptor.AbstractPerfTestQueue; import com.lmax.disruptor.support.Operation; import com.lmax.disruptor.support.ValueMutationQueueProcessor; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import static com.lmax.disruptor.support.PerfTestUtil.failIf; /** *
 *
 * MultiCast a series of items between 1 publisher and 3 event processors.
 *
 *           +-----+
 *    +----->| EP1 |
 *    |      +-----+
 *    |
 * +----+    +-----+
 * | P1 |--->| EP2 |
 * +----+    +-----+
 *    |
 *    |      +-----+
 *    +----->| EP3 |
 *           +-----+
 *
 *
 * Queue Based:
 * ============
 *                 take
 *   put     +====+    +-----+
 *    +----->| Q1 |<---| EP1 |
 *    |      +====+    +-----+
 *    |
 * +----+    +====+    +-----+
 * | P1 |--->| Q2 |<---| EP2 |
 * +----+    +====+    +-----+
 *    |
 *    |      +====+    +-----+
 *    +----->| Q3 |<---| EP3 |
 *           +====+    +-----+
 *
 * P1  - Publisher 1
 * Q1  - Queue 1
 * Q2  - Queue 2
 * Q3  - Queue 3
 * EP1 - EventProcessor 1
 * EP2 - EventProcessor 2
 * EP3 - EventProcessor 3
 *
 * 
*/ public final class OneToThreeQueueThroughputTest extends AbstractPerfTestQueue { private static final int NUM_EVENT_PROCESSORS = 3; private static final int BUFFER_SIZE = 1024 * 8; private static final long ITERATIONS = 1000L * 1000L * 1L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_EVENT_PROCESSORS, DaemonThreadFactory.INSTANCE); private final long[] results = new long[NUM_EVENT_PROCESSORS]; { for (long i = 0; i < ITERATIONS; i++) { results[0] = Operation.ADDITION.op(results[0], i); results[1] = Operation.SUBTRACTION.op(results[1], i); results[2] = Operation.AND.op(results[2], i); } } /////////////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("unchecked") private final BlockingQueue[] blockingQueues = new BlockingQueue[NUM_EVENT_PROCESSORS]; { blockingQueues[0] = new LinkedBlockingQueue<>(BUFFER_SIZE); blockingQueues[1] = new LinkedBlockingQueue<>(BUFFER_SIZE); blockingQueues[2] = new LinkedBlockingQueue<>(BUFFER_SIZE); } private final ValueMutationQueueProcessor[] queueProcessors = new ValueMutationQueueProcessor[NUM_EVENT_PROCESSORS]; { queueProcessors[0] = new ValueMutationQueueProcessor(blockingQueues[0], Operation.ADDITION, ITERATIONS - 1); queueProcessors[1] = new ValueMutationQueueProcessor(blockingQueues[1], Operation.SUBTRACTION, ITERATIONS - 1); queueProcessors[2] = new ValueMutationQueueProcessor(blockingQueues[2], Operation.AND, ITERATIONS - 1); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected long runQueuePass() throws InterruptedException { CountDownLatch latch = new CountDownLatch(NUM_EVENT_PROCESSORS); Future[] futures = new Future[NUM_EVENT_PROCESSORS]; for (int i = 0; i < NUM_EVENT_PROCESSORS; i++) { queueProcessors[i].reset(latch); futures[i] = executor.submit(queueProcessors[i]); } long start = System.currentTimeMillis(); for (long i = 0; i < ITERATIONS; i++) { final Long value = Long.valueOf(i); for (BlockingQueue queue : blockingQueues) { queue.put(value); } } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); for (int i = 0; i < NUM_EVENT_PROCESSORS; i++) { queueProcessors[i].halt(); futures[i].cancel(true); failIf(queueProcessors[i].getValue(), -1); } return opsPerSecond; } public static void main(final String[] args) throws Exception { new OneToThreeQueueThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/queue/PingPongQueueLatencyTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.queue; import com.lmax.disruptor.util.DaemonThreadFactory; import org.HdrHistogram.Histogram; import java.io.PrintStream; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** *
 *
 * Ping pongs between 2 event handlers and measures the latency of
 * a round trip.
 *
 * Queue Based:
 * ============
 *               +---take---+
 *               |          |
 *               |          V
 *            +====+      +====+
 *    +------>| Q1 |      | P2 |-------+
 *    |       +====+      +====+       |
 *   put                              put
 *    |       +====+      +====+       |
 *    +-------| P1 |      | Q2 |<------+
 *            +====+      +====+
 *               ^          |
 *               |          |
 *               +---take---+
 *
 * P1 - QueuePinger
 * P2 - QueuePonger
 * Q1 - PingQueue
 * Q2 - PongQueue
 *
 * 
* *

Note: This test is only useful on a system using an invariant TSC in user space from the System.nanoTime() call. */ public final class PingPongQueueLatencyTest { private static final int BUFFER_SIZE = 1024; private static final long ITERATIONS = 100L * 1000L * 30L; private static final long PAUSE_NANOS = 1000L; private final ExecutorService executor = Executors.newCachedThreadPool(DaemonThreadFactory.INSTANCE); private final Histogram histogram = new Histogram(10000000000L, 4); /////////////////////////////////////////////////////////////////////////////////////////////// private final BlockingQueue pingQueue = new ArrayBlockingQueue<>(BUFFER_SIZE); private final BlockingQueue pongQueue = new ArrayBlockingQueue<>(BUFFER_SIZE); private final QueuePinger qPinger = new QueuePinger(pingQueue, pongQueue, ITERATIONS, PAUSE_NANOS); private final QueuePonger qPonger = new QueuePonger(pingQueue, pongQueue); /////////////////////////////////////////////////////////////////////////////////////////////// public void testImplementation() throws Exception { final int runs = 3; for (int i = 0; i < runs; i++) { System.gc(); histogram.reset(); runQueuePass(); System.out.format("%s run %d BlockingQueue %s\n", getClass().getSimpleName(), Long.valueOf(i), histogram); dumpHistogram(histogram, System.out); } } private static void dumpHistogram(final Histogram histogram, final PrintStream out) { histogram.outputPercentileDistribution(out, 1, 1000.0); } private void runQueuePass() throws Exception { final CountDownLatch latch = new CountDownLatch(1); final CyclicBarrier barrier = new CyclicBarrier(3); qPinger.reset(barrier, latch, histogram); qPonger.reset(barrier); final Future pingFuture = executor.submit(qPinger); final Future pongFuture = executor.submit(qPonger); barrier.await(); latch.await(); pingFuture.cancel(true); pongFuture.cancel(true); } public static void main(final String[] args) throws Exception { final PingPongQueueLatencyTest test = new PingPongQueueLatencyTest(); test.testImplementation(); } private static class QueuePinger implements Runnable { private final BlockingQueue pingQueue; private final BlockingQueue pongQueue; private final long pauseTimeNs; private Histogram histogram; private CyclicBarrier barrier; private CountDownLatch latch; private long counter; private final long maxEvents; QueuePinger( final BlockingQueue pingQueue, final BlockingQueue pongQueue, final long maxEvents, final long pauseTimeNs) { this.pingQueue = pingQueue; this.pongQueue = pongQueue; this.maxEvents = maxEvents; this.pauseTimeNs = pauseTimeNs; } @Override public void run() { try { barrier.await(); Thread.sleep(1000); long counter = 0; while (counter < maxEvents) { final long t0 = System.nanoTime(); pingQueue.put(1L); counter += pongQueue.take(); final long t1 = System.nanoTime(); histogram.recordValueWithExpectedInterval(t1 - t0, pauseTimeNs); while (pauseTimeNs > (System.nanoTime() - t1)) { Thread.yield(); } } latch.countDown(); } catch (final Exception e) { e.printStackTrace(); return; } } public void reset(final CyclicBarrier barrier, final CountDownLatch latch, final Histogram histogram) { this.histogram = histogram; this.barrier = barrier; this.latch = latch; counter = 0; } } private static class QueuePonger implements Runnable { private final BlockingQueue pingQueue; private final BlockingQueue pongQueue; private CyclicBarrier barrier; QueuePonger(final BlockingQueue pingQueue, final BlockingQueue pongQueue) { this.pingQueue = pingQueue; this.pongQueue = pongQueue; } @Override public void run() { final Thread thread = Thread.currentThread(); try { barrier.await(); while (!thread.isInterrupted()) { final Long value = pingQueue.take(); pongQueue.put(value); } } catch (final InterruptedException e) { // do-nothing. } catch (final Exception e) { e.printStackTrace(); } } public void reset(final CyclicBarrier barrier) { this.barrier = barrier; } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/queue/ThreeToOneQueueBatchThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.queue; import com.lmax.disruptor.AbstractPerfTestQueue; import com.lmax.disruptor.support.ValueAdditionQueueBatchProcessor; import com.lmax.disruptor.support.ValueQueuePublisher; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** *

 *
 * Sequence a series of events from multiple publishers going to one event processor.
 *
 * +----+
 * | P1 |------+
 * +----+      |
 *             v
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *             ^
 * +----+      |
 * | P3 |------+
 * +----+
 *
 *
 * Queue Based:
 * ============
 *
 * +----+  put
 * | P1 |------+
 * +----+      |
 *             v   take
 * +----+    +====+    +-----+
 * | P2 |--->| Q1 |<---| EP1 |
 * +----+    +====+    +-----+
 *             ^
 * +----+      |
 * | P3 |------+
 * +----+
 *
 * P1  - Publisher 1
 * P2  - Publisher 2
 * P3  - Publisher 3
 * Q1  - Queue 1
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class ThreeToOneQueueBatchThroughputTest extends AbstractPerfTestQueue { private static final int NUM_PUBLISHERS = 3; private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 20L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_PUBLISHERS + 1, DaemonThreadFactory.INSTANCE); private final CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM_PUBLISHERS + 1); /////////////////////////////////////////////////////////////////////////////////////////////// private final BlockingQueue blockingQueue = new ArrayBlockingQueue<>(BUFFER_SIZE); private final ValueAdditionQueueBatchProcessor queueProcessor = new ValueAdditionQueueBatchProcessor(blockingQueue, ((ITERATIONS / NUM_PUBLISHERS) * NUM_PUBLISHERS) - 1L); private final ValueQueuePublisher[] valueQueuePublishers = new ValueQueuePublisher[NUM_PUBLISHERS]; { for (int i = 0; i < NUM_PUBLISHERS; i++) { valueQueuePublishers[i] = new ValueQueuePublisher(cyclicBarrier, blockingQueue, ITERATIONS / NUM_PUBLISHERS); } } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected long runQueuePass() throws Exception { final CountDownLatch latch = new CountDownLatch(1); queueProcessor.reset(latch); Future[] futures = new Future[NUM_PUBLISHERS]; for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i] = executor.submit(valueQueuePublishers[i]); } Future processorFuture = executor.submit(queueProcessor); long start = System.currentTimeMillis(); cyclicBarrier.await(); for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i].get(); } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); queueProcessor.halt(); processorFuture.cancel(true); return opsPerSecond; } public static void main(final String[] args) throws Exception { new ThreeToOneQueueBatchThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/queue/ThreeToOneQueueThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.queue; import com.lmax.disruptor.AbstractPerfTestQueue; import com.lmax.disruptor.support.ValueAdditionQueueProcessor; import com.lmax.disruptor.support.ValueQueuePublisher; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** *
 *
 * Sequence a series of events from multiple publishers going to one event processor.
 *
 * +----+
 * | P1 |------+
 * +----+      |
 *             v
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *             ^
 * +----+      |
 * | P3 |------+
 * +----+
 *
 *
 * Queue Based:
 * ============
 *
 * +----+  put
 * | P1 |------+
 * +----+      |
 *             v   take
 * +----+    +====+    +-----+
 * | P2 |--->| Q1 |<---| EP1 |
 * +----+    +====+    +-----+
 *             ^
 * +----+      |
 * | P3 |------+
 * +----+
 *
 * P1  - Publisher 1
 * P2  - Publisher 2
 * P3  - Publisher 3
 * Q1  - Queue 1
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class ThreeToOneQueueThroughputTest extends AbstractPerfTestQueue { private static final int NUM_PUBLISHERS = 3; private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 20L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_PUBLISHERS + 1, DaemonThreadFactory.INSTANCE); private final CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM_PUBLISHERS + 1); /////////////////////////////////////////////////////////////////////////////////////////////// private final BlockingQueue blockingQueue = new ArrayBlockingQueue<>(BUFFER_SIZE); private final ValueAdditionQueueProcessor queueProcessor = new ValueAdditionQueueProcessor(blockingQueue, ((ITERATIONS / NUM_PUBLISHERS) * NUM_PUBLISHERS) - 1L); private final ValueQueuePublisher[] valueQueuePublishers = new ValueQueuePublisher[NUM_PUBLISHERS]; { for (int i = 0; i < NUM_PUBLISHERS; i++) { valueQueuePublishers[i] = new ValueQueuePublisher(cyclicBarrier, blockingQueue, ITERATIONS / NUM_PUBLISHERS); } } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected long runQueuePass() throws Exception { final CountDownLatch latch = new CountDownLatch(1); queueProcessor.reset(latch); Future[] futures = new Future[NUM_PUBLISHERS]; for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i] = executor.submit(valueQueuePublishers[i]); } Future processorFuture = executor.submit(queueProcessor); long start = System.currentTimeMillis(); cyclicBarrier.await(); for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i].get(); } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); queueProcessor.halt(); processorFuture.cancel(true); return opsPerSecond; } public static void main(final String[] args) throws Exception { new ThreeToOneQueueThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/raw/OneToOneRawBatchThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.raw; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.Sequenced; import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.SingleProducerSequencer; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** *
 * UniCast a series of items between 1 publisher and 1 event processor.
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 *
 * Queue Based:
 * ============
 *
 *        put      take
 * +----+    +====+    +-----+
 * | P1 |--->| Q1 |<---| EP1 |
 * +----+    +====+    +-----+
 *
 * P1  - Publisher 1
 * Q1  - Queue 1
 * EP1 - EventProcessor 1
 *
 *
 * Disruptor:
 * ==========
 *              track to prevent wrap
 *              +------------------+
 *              |                  |
 *              |                  v
 * +----+    +====+    +====+   +-----+
 * | P1 |--->| RB |<---| SB |   | EP1 |
 * +----+    +====+    +====+   +-----+
 *      claim      get    ^        |
 *                        |        |
 *                        +--------+
 *                          waitFor
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneRawBatchThroughputTest extends AbstractPerfTestDisruptor { private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 200L; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); /////////////////////////////////////////////////////////////////////////////////////////////// private final Sequencer sequencer = new SingleProducerSequencer(BUFFER_SIZE, new YieldingWaitStrategy()); private final MyRunnable myRunnable = new MyRunnable(sequencer); { sequencer.addGatingSequences(myRunnable.sequence); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); int batchSize = 10; final CountDownLatch latch = new CountDownLatch(1); long expectedCount = myRunnable.sequence.get() + (ITERATIONS * batchSize); myRunnable.reset(latch, expectedCount); executor.submit(myRunnable); long start = System.currentTimeMillis(); final Sequenced sequencer = this.sequencer; for (long i = 0; i < ITERATIONS; i++) { long next = sequencer.next(batchSize); sequencer.publish((next - (batchSize - 1)), next); } latch.await(); long end = System.currentTimeMillis(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L * batchSize) / (end - start)); waitForEventProcessorSequence(expectedCount); return perfTestContext; } private void waitForEventProcessorSequence(final long expectedCount) throws InterruptedException { while (myRunnable.sequence.get() != expectedCount) { Thread.sleep(1); } } private static class MyRunnable implements Runnable { private CountDownLatch latch; private long expectedCount; Sequence sequence = new Sequence(-1); private final SequenceBarrier barrier; MyRunnable(final Sequencer sequencer) { this.barrier = sequencer.newBarrier(); } public void reset(final CountDownLatch latch, final long expectedCount) { this.latch = latch; this.expectedCount = expectedCount; } @Override public void run() { long expected = expectedCount; long processed = -1; try { do { processed = barrier.waitFor(sequence.get() + 1); sequence.set(processed); } while (processed < expected); latch.countDown(); sequence.set(processed); } catch (Exception e) { e.printStackTrace(); } } } public static void main(final String[] args) throws Exception { OneToOneRawBatchThroughputTest test = new OneToOneRawBatchThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/raw/OneToOneRawThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.raw; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.Sequenced; import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.SingleProducerSequencer; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** *
 * UniCast a series of items between 1 publisher and 1 event processor.
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 *
 * Queue Based:
 * ============
 *
 *        put      take
 * +----+    +====+    +-----+
 * | P1 |--->| Q1 |<---| EP1 |
 * +----+    +====+    +-----+
 *
 * P1  - Publisher 1
 * Q1  - Queue 1
 * EP1 - EventProcessor 1
 *
 *
 * Disruptor:
 * ==========
 *              track to prevent wrap
 *              +------------------+
 *              |                  |
 *              |                  v
 * +----+    +====+    +====+   +-----+
 * | P1 |--->| RB |<---| SB |   | EP1 |
 * +----+    +====+    +====+   +-----+
 *      claim      get    ^        |
 *                        |        |
 *                        +--------+
 *                          waitFor
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneRawThroughputTest extends AbstractPerfTestDisruptor { private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 200L; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); /////////////////////////////////////////////////////////////////////////////////////////////// private final Sequencer sequencer = new SingleProducerSequencer(BUFFER_SIZE, new YieldingWaitStrategy()); private final MyRunnable myRunnable = new MyRunnable(sequencer); { sequencer.addGatingSequences(myRunnable.sequence); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); final CountDownLatch latch = new CountDownLatch(1); long expectedCount = myRunnable.sequence.get() + ITERATIONS; myRunnable.reset(latch, expectedCount); executor.submit(myRunnable); long start = System.currentTimeMillis(); final Sequenced sequencer = this.sequencer; for (long i = 0; i < ITERATIONS; i++) { long next = sequencer.next(); sequencer.publish(next); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); waitForEventProcessorSequence(expectedCount); return perfTestContext; } private void waitForEventProcessorSequence(final long expectedCount) throws InterruptedException { while (myRunnable.sequence.get() != expectedCount) { Thread.sleep(1); } } private static class MyRunnable implements Runnable { private CountDownLatch latch; private long expectedCount; Sequence sequence = new Sequence(-1); private final SequenceBarrier barrier; MyRunnable(final Sequencer sequencer) { this.barrier = sequencer.newBarrier(); } public void reset(final CountDownLatch latch, final long expectedCount) { this.latch = latch; this.expectedCount = expectedCount; } @Override public void run() { long expected = expectedCount; long processed = -1; try { do { processed = barrier.waitFor(sequence.get() + 1); sequence.set(processed); } while (processed < expected); latch.countDown(); sequence.setVolatile(processed); } catch (Exception e) { e.printStackTrace(); } } } public static void main(final String[] args) throws Exception { OneToOneRawThroughputTest test = new OneToOneRawThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/OneToOneSequencedBatchThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.support.PerfTestUtil; import com.lmax.disruptor.support.ValueAdditionEventHandler; import com.lmax.disruptor.support.ValueEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lmax.disruptor.RingBuffer.createSingleProducer; import static com.lmax.disruptor.support.PerfTestUtil.failIfNot; /** *
 * UniCast a series of items between 1 publisher and 1 event processor.
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 * Disruptor:
 * ==========
 *              track to prevent wrap
 *              +------------------+
 *              |                  |
 *              |                  v
 * +----+    +====+    +====+   +-----+
 * | P1 |--->| RB |<---| SB |   | EP1 |
 * +----+    +====+    +====+   +-----+
 *      claim      get    ^        |
 *                        |        |
 *                        +--------+
 *                          waitFor
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneSequencedBatchThroughputTest extends AbstractPerfTestDisruptor { public static final int BATCH_SIZE = 10; private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 100L; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); private final long expectedResult = PerfTestUtil.accumulatedAddition(ITERATIONS) * BATCH_SIZE; /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final ValueAdditionEventHandler handler = new ValueAdditionEventHandler(); private final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handler); { ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); final CountDownLatch latch = new CountDownLatch(1); long expectedCount = batchEventProcessor.getSequence().get() + ITERATIONS * BATCH_SIZE; handler.reset(latch, expectedCount); executor.submit(batchEventProcessor); long start = System.currentTimeMillis(); final RingBuffer rb = ringBuffer; for (long i = 0; i < ITERATIONS; i++) { long hi = rb.next(BATCH_SIZE); long lo = hi - (BATCH_SIZE - 1); for (long l = lo; l <= hi; l++) { rb.get(l).setValue(i); } rb.publish(lo, hi); } latch.await(); perfTestContext.setDisruptorOps((BATCH_SIZE * ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS * BATCH_SIZE); waitForEventProcessorSequence(expectedCount); batchEventProcessor.halt(); failIfNot(expectedResult, handler.getValue()); return perfTestContext; } private void waitForEventProcessorSequence(final long expectedCount) throws InterruptedException { while (batchEventProcessor.getSequence().get() != expectedCount) { Thread.sleep(1); } } public static void main(final String[] args) throws Exception { OneToOneSequencedBatchThroughputTest test = new OneToOneSequencedBatchThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/OneToOneSequencedLongArrayThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.support.LongArrayEventHandler; import com.lmax.disruptor.support.PerfTestUtil; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lmax.disruptor.RingBuffer.createSingleProducer; /** *
 * UniCast a series of items between 1 publisher and 1 event processor.
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 * Disruptor:
 * ==========
 *              track to prevent wrap
 *              +------------------+
 *              |                  |
 *              |                  v
 * +----+    +====+    +====+   +-----+
 * | P1 |--->| RB |<---| SB |   | EP1 |
 * +----+    +====+    +====+   +-----+
 *      claim      get    ^        |
 *                        |        |
 *                        +--------+
 *                          waitFor
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneSequencedLongArrayThroughputTest extends AbstractPerfTestDisruptor { private static final int BUFFER_SIZE = 1024 * 1; private static final long ITERATIONS = 1000L * 1000L * 1L; private static final int ARRAY_SIZE = 2 * 1024; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); private static final EventFactory FACTORY = () -> new long[ARRAY_SIZE]; /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createSingleProducer(FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final LongArrayEventHandler handler = new LongArrayEventHandler(); private final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handler); { ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); final CountDownLatch latch = new CountDownLatch(1); long expectedCount = batchEventProcessor.getSequence().get() + ITERATIONS; handler.reset(latch, ITERATIONS); executor.submit(batchEventProcessor); long start = System.currentTimeMillis(); final RingBuffer rb = ringBuffer; for (long i = 0; i < ITERATIONS; i++) { long next = rb.next(); long[] event = rb.get(next); for (int j = 0; j < event.length; j++) { event[j] = i; } rb.publish(next); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * ARRAY_SIZE * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS); waitForEventProcessorSequence(expectedCount); batchEventProcessor.halt(); PerfTestUtil.failIf(0, handler.getValue()); return perfTestContext; } private void waitForEventProcessorSequence(final long expectedCount) throws InterruptedException { while (batchEventProcessor.getSequence().get() != expectedCount) { Thread.sleep(1); } } public static void main(final String[] args) throws Exception { OneToOneSequencedLongArrayThroughputTest test = new OneToOneSequencedLongArrayThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/OneToOneSequencedPollerThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.EventPoller; import com.lmax.disruptor.EventPoller.PollState; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.support.PerfTestUtil; import com.lmax.disruptor.support.ValueEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import com.lmax.disruptor.util.PaddedLong; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lmax.disruptor.RingBuffer.createSingleProducer; import static com.lmax.disruptor.support.PerfTestUtil.failIfNot; /** *
 * UniCast a series of items between 1 publisher and 1 event processor.
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 * Disruptor:
 * ==========
 *              track to prevent wrap
 *              +------------------+
 *              |                  |
 *              |                  v
 * +----+    +====+    +====+   +-----+
 * | P1 |--->| RB |<---| SB |   | EP1 |
 * +----+    +====+    +====+   +-----+
 *      claim      get    ^        |
 *                        |        |
 *                        +--------+
 *                          waitFor
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneSequencedPollerThroughputTest extends AbstractPerfTestDisruptor { private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 100L; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); private final long expectedResult = PerfTestUtil.accumulatedAddition(ITERATIONS); /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); private final EventPoller poller = ringBuffer.newPoller(); private final PollRunnable pollRunnable = new PollRunnable(poller); { ringBuffer.addGatingSequences(poller.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } private static class PollRunnable implements Runnable, EventPoller.Handler { private final EventPoller poller; private volatile boolean running = true; private final PaddedLong value = new PaddedLong(); private final PaddedLong batchesProcessed = new PaddedLong(); private CountDownLatch latch; private long count; PollRunnable(final EventPoller poller) { this.poller = poller; } @Override public void run() { try { while (running) { if (PollState.PROCESSING != poller.poll(this)) { Thread.yield(); } } } catch (Exception e) { e.printStackTrace(); } } @Override public boolean onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) { value.set(value.get() + event.getValue()); if (count == sequence) { latch.countDown(); } return true; } public void halt() { running = false; } public void reset(final CountDownLatch latch, final long expectedCount) { value.set(0L); this.latch = latch; count = expectedCount; batchesProcessed.set(0); running = true; } public long getValue() { return value.get(); } public long getBatchesProcessed() { return batchesProcessed.get(); } public void onBatchStart(final long batchSize) { batchesProcessed.increment(); } } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); final CountDownLatch latch = new CountDownLatch(1); long expectedCount = poller.getSequence().get() + ITERATIONS; pollRunnable.reset(latch, expectedCount); executor.submit(pollRunnable); long start = System.currentTimeMillis(); final RingBuffer rb = ringBuffer; for (long i = 0; i < ITERATIONS; i++) { long next = rb.next(); rb.get(next).setValue(i); rb.publish(next); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(pollRunnable.getBatchesProcessed(), ITERATIONS); waitForEventProcessorSequence(expectedCount); pollRunnable.halt(); failIfNot(expectedResult, pollRunnable.getValue()); return perfTestContext; } private void waitForEventProcessorSequence(final long expectedCount) throws InterruptedException { while (poller.getSequence().get() != expectedCount) { Thread.sleep(1); } } public static void main(final String[] args) throws Exception { OneToOneSequencedPollerThroughputTest test = new OneToOneSequencedPollerThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/OneToOneSequencedThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.support.PerfTestUtil; import com.lmax.disruptor.support.ValueAdditionEventHandler; import com.lmax.disruptor.support.ValueEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lmax.disruptor.RingBuffer.createSingleProducer; import static com.lmax.disruptor.support.PerfTestUtil.failIfNot; /** *
 * UniCast a series of items between 1 publisher and 1 event processor.
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 * Disruptor:
 * ==========
 *              track to prevent wrap
 *              +------------------+
 *              |                  |
 *              |                  v
 * +----+    +====+    +====+   +-----+
 * | P1 |--->| RB |<---| SB |   | EP1 |
 * +----+    +====+    +====+   +-----+
 *      claim      get    ^        |
 *                        |        |
 *                        +--------+
 *                          waitFor
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneSequencedThroughputTest extends AbstractPerfTestDisruptor { private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 100L; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); private final long expectedResult = PerfTestUtil.accumulatedAddition(ITERATIONS); /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final ValueAdditionEventHandler handler = new ValueAdditionEventHandler(); private final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handler); { ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); final CountDownLatch latch = new CountDownLatch(1); long expectedCount = batchEventProcessor.getSequence().get() + ITERATIONS; handler.reset(latch, expectedCount); executor.submit(batchEventProcessor); long start = System.currentTimeMillis(); final RingBuffer rb = ringBuffer; for (long i = 0; i < ITERATIONS; i++) { long next = rb.next(); rb.get(next).setValue(i); rb.publish(next); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS); waitForEventProcessorSequence(expectedCount); batchEventProcessor.halt(); failIfNot(expectedResult, handler.getValue()); return perfTestContext; } private void waitForEventProcessorSequence(final long expectedCount) throws InterruptedException { while (batchEventProcessor.getSequence().get() != expectedCount) { Thread.sleep(1); } } public static void main(final String[] args) throws Exception { OneToOneSequencedThroughputTest test = new OneToOneSequencedThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/OneToThreeDiamondSequencedThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.support.FizzBuzzEvent; import com.lmax.disruptor.support.FizzBuzzEventHandler; import com.lmax.disruptor.support.FizzBuzzStep; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lmax.disruptor.RingBuffer.createSingleProducer; import static com.lmax.disruptor.support.PerfTestUtil.failIfNot; /** *
 * Produce an event replicated to two event processors and fold back to a single third event processor.
 *
 *           +-----+
 *    +----->| EP1 |------+
 *    |      +-----+      |
 *    |                   v
 * +----+              +-----+
 * | P1 |              | EP3 |
 * +----+              +-----+
 *    |                   ^
 *    |      +-----+      |
 *    +----->| EP2 |------+
 *           +-----+
 *
 * Disruptor:
 * ==========
 *                    track to prevent wrap
 *              +-------------------------------+
 *              |                               |
 *              |                               v
 * +----+    +====+               +=====+    +-----+
 * | P1 |--->| RB |<--------------| SB2 |<---| EP3 |
 * +----+    +====+               +=====+    +-----+
 *      claim   ^  get               |   waitFor
 *              |                    |
 *           +=====+    +-----+      |
 *           | SB1 |<---| EP1 |<-----+
 *           +=====+    +-----+      |
 *              ^                    |
 *              |       +-----+      |
 *              +-------| EP2 |<-----+
 *             waitFor  +-----+
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB1 - SequenceBarrier 1
 * EP1 - EventProcessor 1
 * EP2 - EventProcessor 2
 * SB2 - SequenceBarrier 2
 * EP3 - EventProcessor 3
 *
 * 
*/ public final class OneToThreeDiamondSequencedThroughputTest extends AbstractPerfTestDisruptor { private static final int NUM_EVENT_PROCESSORS = 3; private static final int BUFFER_SIZE = 1024 * 8; private static final long ITERATIONS = 1000L * 1000L * 100L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_EVENT_PROCESSORS, DaemonThreadFactory.INSTANCE); private final long expectedResult; { long temp = 0L; for (long i = 0; i < ITERATIONS; i++) { boolean fizz = 0 == (i % 3L); boolean buzz = 0 == (i % 5L); if (fizz && buzz) { ++temp; } } expectedResult = temp; } /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createSingleProducer(FizzBuzzEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final FizzBuzzEventHandler fizzHandler = new FizzBuzzEventHandler(FizzBuzzStep.FIZZ); private final BatchEventProcessor batchProcessorFizz = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, fizzHandler); private final FizzBuzzEventHandler buzzHandler = new FizzBuzzEventHandler(FizzBuzzStep.BUZZ); private final BatchEventProcessor batchProcessorBuzz = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, buzzHandler); private final SequenceBarrier sequenceBarrierFizzBuzz = ringBuffer.newBarrier(batchProcessorFizz.getSequence(), batchProcessorBuzz.getSequence()); private final FizzBuzzEventHandler fizzBuzzHandler = new FizzBuzzEventHandler(FizzBuzzStep.FIZZ_BUZZ); private final BatchEventProcessor batchProcessorFizzBuzz = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrierFizzBuzz, fizzBuzzHandler); { ringBuffer.addGatingSequences(batchProcessorFizzBuzz.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected PerfTestContext runDisruptorPass() throws Exception { PerfTestContext perfTestContext = new PerfTestContext(); CountDownLatch latch = new CountDownLatch(1); fizzBuzzHandler.reset(latch, batchProcessorFizzBuzz.getSequence().get() + ITERATIONS); executor.submit(batchProcessorFizz); executor.submit(batchProcessorBuzz); executor.submit(batchProcessorFizzBuzz); long start = System.currentTimeMillis(); for (long i = 0; i < ITERATIONS; i++) { long sequence = ringBuffer.next(); ringBuffer.get(sequence).setValue(i); ringBuffer.publish(sequence); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); batchProcessorFizz.halt(); batchProcessorBuzz.halt(); batchProcessorFizzBuzz.halt(); failIfNot(expectedResult, fizzBuzzHandler.getFizzBuzzCounter()); return perfTestContext; } public static void main(final String[] args) throws Exception { new OneToThreeDiamondSequencedThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/OneToThreePipelineSequencedThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.support.FunctionEvent; import com.lmax.disruptor.support.FunctionEventHandler; import com.lmax.disruptor.support.FunctionStep; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lmax.disruptor.RingBuffer.createSingleProducer; import static com.lmax.disruptor.support.PerfTestUtil.failIfNot; /** * Pipeline a series of stages from a publisher to ultimate event processor. * Each event processor depends on the output of the event processor. * *
{@code
 * +----+    +-----+    +-----+    +-----+
 * | P1 |--->| EP1 |--->| EP2 |--->| EP3 |
 * +----+    +-----+    +-----+    +-----+
 *
 * Disruptor:
 * ==========
 *                           track to prevent wrap
 *              +----------------------------------------------------------------+
 *              |                                                                |
 *              |                                                                v
 * +----+    +====+    +=====+    +-----+    +=====+    +-----+    +=====+    +-----+
 * | P1 |--->| RB |    | SB1 |<---| EP1 |<---| SB2 |<---| EP2 |<---| SB3 |<---| EP3 |
 * +----+    +====+    +=====+    +-----+    +=====+    +-----+    +=====+    +-----+
 *      claim   ^  get    |   waitFor           |   waitFor           |  waitFor
 *              |         |                     |                     |
 *              +---------+---------------------+---------------------+
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB1 - SequenceBarrier 1
 * EP1 - EventProcessor 1
 * SB2 - SequenceBarrier 2
 * EP2 - EventProcessor 2
 * SB3 - SequenceBarrier 3
 * EP3 - EventProcessor 3
 * }
*/ public final class OneToThreePipelineSequencedThroughputTest extends AbstractPerfTestDisruptor { private static final int NUM_EVENT_PROCESSORS = 3; private static final int BUFFER_SIZE = 1024 * 8; private static final long ITERATIONS = 1000L * 1000L * 100L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_EVENT_PROCESSORS, DaemonThreadFactory.INSTANCE); private static final long OPERAND_TWO_INITIAL_VALUE = 777L; private final long expectedResult; { long temp = 0L; long operandTwo = OPERAND_TWO_INITIAL_VALUE; for (long i = 0; i < ITERATIONS; i++) { long stepOneResult = i + operandTwo--; long stepTwoResult = stepOneResult + 3; if ((stepTwoResult & 4L) == 4L) { ++temp; } } expectedResult = temp; } /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createSingleProducer(FunctionEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); private final SequenceBarrier stepOneSequenceBarrier = ringBuffer.newBarrier(); private final FunctionEventHandler stepOneFunctionHandler = new FunctionEventHandler(FunctionStep.ONE); private final BatchEventProcessor stepOneBatchProcessor = new BatchEventProcessorBuilder().build(ringBuffer, stepOneSequenceBarrier, stepOneFunctionHandler); private final SequenceBarrier stepTwoSequenceBarrier = ringBuffer.newBarrier(stepOneBatchProcessor.getSequence()); private final FunctionEventHandler stepTwoFunctionHandler = new FunctionEventHandler(FunctionStep.TWO); private final BatchEventProcessor stepTwoBatchProcessor = new BatchEventProcessorBuilder().build(ringBuffer, stepTwoSequenceBarrier, stepTwoFunctionHandler); private final SequenceBarrier stepThreeSequenceBarrier = ringBuffer.newBarrier(stepTwoBatchProcessor.getSequence()); private final FunctionEventHandler stepThreeFunctionHandler = new FunctionEventHandler(FunctionStep.THREE); private final BatchEventProcessor stepThreeBatchProcessor = new BatchEventProcessorBuilder().build(ringBuffer, stepThreeSequenceBarrier, stepThreeFunctionHandler); { ringBuffer.addGatingSequences(stepThreeBatchProcessor.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); CountDownLatch latch = new CountDownLatch(1); stepThreeFunctionHandler.reset(latch, stepThreeBatchProcessor.getSequence().get() + ITERATIONS); executor.submit(stepOneBatchProcessor); executor.submit(stepTwoBatchProcessor); executor.submit(stepThreeBatchProcessor); long start = System.currentTimeMillis(); long operandTwo = OPERAND_TWO_INITIAL_VALUE; for (long i = 0; i < ITERATIONS; i++) { long sequence = ringBuffer.next(); FunctionEvent event = ringBuffer.get(sequence); event.setOperandOne(i); event.setOperandTwo(operandTwo--); ringBuffer.publish(sequence); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); stepOneBatchProcessor.halt(); stepTwoBatchProcessor.halt(); stepThreeBatchProcessor.halt(); failIfNot(expectedResult, stepThreeFunctionHandler.getStepThreeCounter()); return perfTestContext; } public static void main(final String[] args) throws Exception { new OneToThreePipelineSequencedThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/OneToThreeSequencedThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.support.Operation; import com.lmax.disruptor.support.ValueEvent; import com.lmax.disruptor.support.ValueMutationEventHandler; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lmax.disruptor.RingBuffer.createSingleProducer; import static com.lmax.disruptor.support.PerfTestUtil.failIfNot; /** *
 *
 * MultiCast a series of items between 1 publisher and 3 event processors.
 *
 *           +-----+
 *    +----->| EP1 |
 *    |      +-----+
 *    |
 * +----+    +-----+
 * | P1 |--->| EP2 |
 * +----+    +-----+
 *    |
 *    |      +-----+
 *    +----->| EP3 |
 *           +-----+
 *
 * Disruptor:
 * ==========
 *                             track to prevent wrap
 *             +--------------------+----------+----------+
 *             |                    |          |          |
 *             |                    v          v          v
 * +----+    +====+    +====+    +-----+    +-----+    +-----+
 * | P1 |--->| RB |<---| SB |    | EP1 |    | EP2 |    | EP3 |
 * +----+    +====+    +====+    +-----+    +-----+    +-----+
 *      claim      get    ^         |          |          |
 *                        |         |          |          |
 *                        +---------+----------+----------+
 *                                      waitFor
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 * EP2 - EventProcessor 2
 * EP3 - EventProcessor 3
 *
 * 
*/ public final class OneToThreeSequencedThroughputTest extends AbstractPerfTestDisruptor { private static final int NUM_EVENT_PROCESSORS = 3; private static final int BUFFER_SIZE = 1024 * 8; private static final long ITERATIONS = 1000L * 1000L * 100L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_EVENT_PROCESSORS, DaemonThreadFactory.INSTANCE); private final long[] results = new long[NUM_EVENT_PROCESSORS]; { for (long i = 0; i < ITERATIONS; i++) { results[0] = Operation.ADDITION.op(results[0], i); results[1] = Operation.SUBTRACTION.op(results[1], i); results[2] = Operation.AND.op(results[2], i); } } /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final ValueMutationEventHandler[] handlers = new ValueMutationEventHandler[NUM_EVENT_PROCESSORS]; { handlers[0] = new ValueMutationEventHandler(Operation.ADDITION); handlers[1] = new ValueMutationEventHandler(Operation.SUBTRACTION); handlers[2] = new ValueMutationEventHandler(Operation.AND); } private final BatchEventProcessor[] batchEventProcessors = new BatchEventProcessor[NUM_EVENT_PROCESSORS]; { batchEventProcessors[0] = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handlers[0]); batchEventProcessors[1] = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handlers[1]); batchEventProcessors[2] = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handlers[2]); ringBuffer.addGatingSequences( batchEventProcessors[0].getSequence(), batchEventProcessors[1].getSequence(), batchEventProcessors[2].getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); CountDownLatch latch = new CountDownLatch(NUM_EVENT_PROCESSORS); for (int i = 0; i < NUM_EVENT_PROCESSORS; i++) { handlers[i].reset(latch, batchEventProcessors[i].getSequence().get() + ITERATIONS); executor.submit(batchEventProcessors[i]); } long start = System.currentTimeMillis(); for (long i = 0; i < ITERATIONS; i++) { long sequence = ringBuffer.next(); ringBuffer.get(sequence).setValue(i); ringBuffer.publish(sequence); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(sumBatches(handlers), ITERATIONS * handlers.length); for (int i = 0; i < NUM_EVENT_PROCESSORS; i++) { batchEventProcessors[i].halt(); failIfNot(results[i], handlers[i].getValue()); } return perfTestContext; } private long sumBatches(final ValueMutationEventHandler[] handlers) { long sum = 0; for (ValueMutationEventHandler handler : handlers) { sum += handler.getBatchesProcessed(); } return sum; } public static void main(final String[] args) throws Exception { new OneToThreeSequencedThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/PingPongSequencedLatencyTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.support.ValueEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import org.HdrHistogram.Histogram; import java.io.PrintStream; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lmax.disruptor.RingBuffer.createSingleProducer; /** *
 *
 * Ping pongs between 2 event handlers and measures the latency of
 * a round trip.
 *
 * Disruptor:
 * ==========
 *               +----------+
 *               |          |
 *               |   get    V
 *  waitFor   +=====+    +=====+  claim
 *    +------>| SB2 |    | RB2 |<------+
 *    |       +=====+    +=====+       |
 *    |                                |
 * +-----+    +=====+    +=====+    +-----+
 * | EP1 |--->| RB1 |    | SB1 |<---| EP2 |
 * +-----+    +=====+    +=====+    +-----+
 *       claim   ^   get    |  waitFor
 *               |          |
 *               +----------+
 *
 * EP1 - Pinger
 * EP2 - Ponger
 * RB1 - PingBuffer
 * SB1 - PingBarrier
 * RB2 - PongBuffer
 * SB2 - PongBarrier
 *
 * 
* *

Note: This test is only useful on a system using an invariant TSC in user space from the System.nanoTime() call. */ public final class PingPongSequencedLatencyTest { private static final int BUFFER_SIZE = 1024; private static final long ITERATIONS = 100L * 1000L * 30L; private static final long PAUSE_NANOS = 1000L; private final ExecutorService executor = Executors.newCachedThreadPool(DaemonThreadFactory.INSTANCE); private final Histogram histogram = new Histogram(10000000000L, 4); /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer pingBuffer = createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new BlockingWaitStrategy()); private final RingBuffer pongBuffer = createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new BlockingWaitStrategy()); private final SequenceBarrier pongBarrier = pongBuffer.newBarrier(); private final Pinger pinger = new Pinger(pingBuffer, ITERATIONS, PAUSE_NANOS); private final BatchEventProcessor pingProcessor = new BatchEventProcessorBuilder().build(pongBuffer, pongBarrier, pinger); private final SequenceBarrier pingBarrier = pingBuffer.newBarrier(); private final Ponger ponger = new Ponger(pongBuffer); private final BatchEventProcessor pongProcessor = new BatchEventProcessorBuilder().build(pingBuffer, pingBarrier, ponger); { pingBuffer.addGatingSequences(pongProcessor.getSequence()); pongBuffer.addGatingSequences(pingProcessor.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// public void shouldCompareDisruptorVsQueues() throws Exception { final int runs = 3; for (int i = 0; i < runs; i++) { System.gc(); histogram.reset(); runDisruptorPass(); System.out.format("%s run %d Disruptor %s\n", getClass().getSimpleName(), Long.valueOf(i), histogram); dumpHistogram(histogram, System.out); } } private static void dumpHistogram(final Histogram histogram, final PrintStream out) { histogram.outputPercentileDistribution(out, 1, 1000.0); } private void runDisruptorPass() throws InterruptedException, BrokenBarrierException { final CountDownLatch latch = new CountDownLatch(1); final CyclicBarrier barrier = new CyclicBarrier(3); pinger.reset(barrier, latch, histogram); ponger.reset(barrier); executor.submit(pongProcessor); executor.submit(pingProcessor); barrier.await(); latch.await(); pingProcessor.halt(); pongProcessor.halt(); } public static void main(final String[] args) throws Exception { final PingPongSequencedLatencyTest test = new PingPongSequencedLatencyTest(); test.shouldCompareDisruptorVsQueues(); } private static class Pinger implements EventHandler { private final RingBuffer buffer; private final long maxEvents; private final long pauseTimeNs; private long counter = 0; private CyclicBarrier barrier; private CountDownLatch latch; private Histogram histogram; private long t0; Pinger(final RingBuffer buffer, final long maxEvents, final long pauseTimeNs) { this.buffer = buffer; this.maxEvents = maxEvents; this.pauseTimeNs = pauseTimeNs; } @Override public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) throws Exception { final long t1 = System.nanoTime(); histogram.recordValueWithExpectedInterval(t1 - t0, pauseTimeNs); if (event.getValue() < maxEvents) { while (pauseTimeNs > (System.nanoTime() - t1)) { Thread.yield(); } send(); } else { latch.countDown(); } } private void send() { t0 = System.nanoTime(); final long next = buffer.next(); buffer.get(next).setValue(counter); buffer.publish(next); counter++; } @Override public void onStart() { try { barrier.await(); Thread.sleep(1000); send(); } catch (final Exception e) { throw new RuntimeException(e); } } @Override public void onShutdown() { } public void reset(final CyclicBarrier barrier, final CountDownLatch latch, final Histogram histogram) { this.histogram = histogram; this.barrier = barrier; this.latch = latch; counter = 0; } } private static class Ponger implements EventHandler { private final RingBuffer buffer; private CyclicBarrier barrier; Ponger(final RingBuffer buffer) { this.buffer = buffer; } @Override public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) throws Exception { final long next = buffer.next(); buffer.get(next).setValue(event.getValue()); buffer.publish(next); } @Override public void onStart() { try { barrier.await(); } catch (final Exception e) { throw new RuntimeException(e); } } @Override public void onShutdown() { } public void reset(final CyclicBarrier barrier) { this.barrier = barrier; } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/ThreeToOneSequencedBatchThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.BusySpinWaitStrategy; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.support.ValueAdditionEventHandler; import com.lmax.disruptor.support.ValueBatchPublisher; import com.lmax.disruptor.support.ValueEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import static com.lmax.disruptor.RingBuffer.createMultiProducer; /** *

 *
 * Sequence a series of events from multiple publishers going to one event processor.
 *
 * +----+
 * | P1 |------+
 * +----+      |
 *             v
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *             ^
 * +----+      |
 * | P3 |------+
 * +----+
 *
 * Disruptor:
 * ==========
 *             track to prevent wrap
 *             +--------------------+
 *             |                    |
 *             |                    v
 * +----+    +====+    +====+    +-----+
 * | P1 |--->| RB |<---| SB |    | EP1 |
 * +----+    +====+    +====+    +-----+
 *             ^   get    ^         |
 * +----+      |          |         |
 * | P2 |------+          +---------+
 * +----+      |            waitFor
 *             |
 * +----+      |
 * | P3 |------+
 * +----+
 *
 * P1  - Publisher 1
 * P2  - Publisher 2
 * P3  - Publisher 3
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
* * @author mikeb01 */ public final class ThreeToOneSequencedBatchThroughputTest extends AbstractPerfTestDisruptor { private static final int NUM_PUBLISHERS = 3; private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 100L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_PUBLISHERS + 1, DaemonThreadFactory.INSTANCE); private final CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM_PUBLISHERS + 1); /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createMultiProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new BusySpinWaitStrategy()); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final ValueAdditionEventHandler handler = new ValueAdditionEventHandler(); private final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handler); private final ValueBatchPublisher[] valuePublishers = new ValueBatchPublisher[NUM_PUBLISHERS]; { for (int i = 0; i < NUM_PUBLISHERS; i++) { valuePublishers[i] = new ValueBatchPublisher(cyclicBarrier, ringBuffer, ITERATIONS / NUM_PUBLISHERS, 10); } ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected PerfTestContext runDisruptorPass() throws Exception { PerfTestContext perfTestContext = new PerfTestContext(); final CountDownLatch latch = new CountDownLatch(1); handler.reset(latch, batchEventProcessor.getSequence().get() + ((ITERATIONS / NUM_PUBLISHERS) * NUM_PUBLISHERS)); Future[] futures = new Future[NUM_PUBLISHERS]; for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i] = executor.submit(valuePublishers[i]); } executor.submit(batchEventProcessor); long start = System.currentTimeMillis(); cyclicBarrier.await(); for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i].get(); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS); batchEventProcessor.halt(); return perfTestContext; } public static void main(final String[] args) throws Exception { new ThreeToOneSequencedBatchThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/ThreeToOneSequencedThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.BusySpinWaitStrategy; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.support.ValueAdditionEventHandler; import com.lmax.disruptor.support.ValueEvent; import com.lmax.disruptor.support.ValuePublisher; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import static com.lmax.disruptor.RingBuffer.createMultiProducer; /** *
 *
 * Sequence a series of events from multiple publishers going to one event processor.
 *
 * +----+
 * | P1 |------+
 * +----+      |
 *             v
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *             ^
 * +----+      |
 * | P3 |------+
 * +----+
 *
 *
 * Disruptor:
 * ==========
 *             track to prevent wrap
 *             +--------------------+
 *             |                    |
 *             |                    v
 * +----+    +====+    +====+    +-----+
 * | P1 |--->| RB |<---| SB |    | EP1 |
 * +----+    +====+    +====+    +-----+
 *             ^   get    ^         |
 * +----+      |          |         |
 * | P2 |------+          +---------+
 * +----+      |            waitFor
 *             |
 * +----+      |
 * | P3 |------+
 * +----+
 *
 * P1  - Publisher 1
 * P2  - Publisher 2
 * P3  - Publisher 3
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class ThreeToOneSequencedThroughputTest extends AbstractPerfTestDisruptor { private static final int NUM_PUBLISHERS = 3; private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 20L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_PUBLISHERS + 1, DaemonThreadFactory.INSTANCE); private final CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM_PUBLISHERS + 1); /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer ringBuffer = createMultiProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new BusySpinWaitStrategy()); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final ValueAdditionEventHandler handler = new ValueAdditionEventHandler(); private final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handler); private final ValuePublisher[] valuePublishers = new ValuePublisher[NUM_PUBLISHERS]; { for (int i = 0; i < NUM_PUBLISHERS; i++) { valuePublishers[i] = new ValuePublisher(cyclicBarrier, ringBuffer, ITERATIONS / NUM_PUBLISHERS); } ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected PerfTestContext runDisruptorPass() throws Exception { PerfTestContext perfTestContext = new PerfTestContext(); final CountDownLatch latch = new CountDownLatch(1); handler .reset(latch, batchEventProcessor.getSequence().get() + ((ITERATIONS / NUM_PUBLISHERS) * NUM_PUBLISHERS)); Future[] futures = new Future[NUM_PUBLISHERS]; for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i] = executor.submit(valuePublishers[i]); } executor.submit(batchEventProcessor); long start = System.currentTimeMillis(); cyclicBarrier.await(); for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i].get(); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS); batchEventProcessor.halt(); return perfTestContext; } public static void main(final String[] args) throws Exception { new ThreeToOneSequencedThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/sequenced/ThreeToThreeSequencedThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.sequenced; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.support.LongArrayEventHandler; import com.lmax.disruptor.support.LongArrayPublisher; import com.lmax.disruptor.support.MultiBufferBatchEventProcessor; import com.lmax.disruptor.util.DaemonThreadFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** *
 *
 * Sequence a series of events from multiple publishers going to one event processor.
 *
 * Disruptor:
 * ==========
 *             track to prevent wrap
 *             +--------------------+
 *             |                    |
 *             |                    |
 * +----+    +====+    +====+       |
 * | P1 |--->| RB |--->| SB |--+    |
 * +----+    +====+    +====+  |    |
 *                             |    v
 * +----+    +====+    +====+  | +----+
 * | P2 |--->| RB |--->| SB |--+>| EP |
 * +----+    +====+    +====+  | +----+
 *                             |
 * +----+    +====+    +====+  |
 * | P3 |--->| RB |--->| SB |--+
 * +----+    +====+    +====+
 *
 * P1 - Publisher 1
 * P2 - Publisher 2
 * P3 - Publisher 3
 * RB - RingBuffer
 * SB - SequenceBarrier
 * EP - EventProcessor
 *
 * 
*/ public final class ThreeToThreeSequencedThroughputTest extends AbstractPerfTestDisruptor { private static final int NUM_PUBLISHERS = 3; private static final int ARRAY_SIZE = 3; private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 180L; private final ExecutorService executor = Executors.newFixedThreadPool(NUM_PUBLISHERS + 1, DaemonThreadFactory.INSTANCE); private final CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM_PUBLISHERS + 1); /////////////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("unchecked") private final RingBuffer[] buffers = new RingBuffer[NUM_PUBLISHERS]; private final SequenceBarrier[] barriers = new SequenceBarrier[NUM_PUBLISHERS]; private final LongArrayPublisher[] valuePublishers = new LongArrayPublisher[NUM_PUBLISHERS]; private final LongArrayEventHandler handler = new LongArrayEventHandler(); private final MultiBufferBatchEventProcessor batchEventProcessor; private static final EventFactory FACTORY = () -> new long[ARRAY_SIZE]; { for (int i = 0; i < NUM_PUBLISHERS; i++) { buffers[i] = RingBuffer.createSingleProducer(FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); barriers[i] = buffers[i].newBarrier(); valuePublishers[i] = new LongArrayPublisher( cyclicBarrier, buffers[i], ITERATIONS / NUM_PUBLISHERS, ARRAY_SIZE); } batchEventProcessor = new MultiBufferBatchEventProcessor<>(buffers, barriers, handler); for (int i = 0; i < NUM_PUBLISHERS; i++) { buffers[i].addGatingSequences(batchEventProcessor.getSequences()[i]); } } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 4; } @Override protected PerfTestContext runDisruptorPass() throws Exception { PerfTestContext perfTestContext = new PerfTestContext(); final CountDownLatch latch = new CountDownLatch(1); handler.reset(latch, ITERATIONS); Future[] futures = new Future[NUM_PUBLISHERS]; for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i] = executor.submit(valuePublishers[i]); } executor.submit(batchEventProcessor); long start = System.currentTimeMillis(); cyclicBarrier.await(); for (int i = 0; i < NUM_PUBLISHERS; i++) { futures[i].get(); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L * ARRAY_SIZE) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS * ARRAY_SIZE); batchEventProcessor.halt(); return perfTestContext; } public static void main(final String[] args) throws Exception { new ThreeToThreeSequencedThroughputTest().testImplementations(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/EventCountingQueueProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.util.PaddedLong; import java.util.concurrent.BlockingQueue; public final class EventCountingQueueProcessor implements Runnable { private volatile boolean running; private final BlockingQueue blockingQueue; private final PaddedLong[] counters; private final int index; public EventCountingQueueProcessor( final BlockingQueue blockingQueue, final PaddedLong[] counters, final int index) { this.blockingQueue = blockingQueue; this.counters = counters; this.index = index; } public void halt() { running = false; } @Override public void run() { running = true; while (running) { try { blockingQueue.take(); counters[index].set(counters[index].get() + 1L); } catch (InterruptedException ex) { break; } } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/FizzBuzzEvent.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventFactory; public final class FizzBuzzEvent { private boolean fizz = false; private boolean buzz = false; private long value = 0; public long getValue() { return value; } public void setValue(final long value) { fizz = false; buzz = false; this.value = value; } public boolean isFizz() { return fizz; } public void setFizz(final boolean fizz) { this.fizz = fizz; } public boolean isBuzz() { return buzz; } public void setBuzz(final boolean buzz) { this.buzz = buzz; } public static final EventFactory EVENT_FACTORY = () -> new FizzBuzzEvent(); } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/FizzBuzzEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.util.PaddedLong; import java.util.concurrent.CountDownLatch; public final class FizzBuzzEventHandler implements EventHandler { private final FizzBuzzStep fizzBuzzStep; private final PaddedLong fizzBuzzCounter = new PaddedLong(); private long count; private CountDownLatch latch; public FizzBuzzEventHandler(final FizzBuzzStep fizzBuzzStep) { this.fizzBuzzStep = fizzBuzzStep; } public void reset(final CountDownLatch latch, final long expectedCount) { fizzBuzzCounter.set(0L); this.latch = latch; count = expectedCount; } public long getFizzBuzzCounter() { return fizzBuzzCounter.get(); } @Override public void onEvent(final FizzBuzzEvent event, final long sequence, final boolean endOfBatch) throws Exception { switch (fizzBuzzStep) { case FIZZ: if (0 == (event.getValue() % 3)) { event.setFizz(true); } break; case BUZZ: if (0 == (event.getValue() % 5)) { event.setBuzz(true); } break; case FIZZ_BUZZ: if (event.isFizz() && event.isBuzz()) { fizzBuzzCounter.set(fizzBuzzCounter.get() + 1L); } break; } if (latch != null && count == sequence) { latch.countDown(); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/FizzBuzzQueueProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; public final class FizzBuzzQueueProcessor implements Runnable { private final FizzBuzzStep fizzBuzzStep; private final BlockingQueue fizzInputQueue; private final BlockingQueue buzzInputQueue; private final BlockingQueue fizzOutputQueue; private final BlockingQueue buzzOutputQueue; private final long count; private volatile boolean running; private long fizzBuzzCounter = 0; private long sequence; private CountDownLatch latch = null; public FizzBuzzQueueProcessor( final FizzBuzzStep fizzBuzzStep, final BlockingQueue fizzInputQueue, final BlockingQueue buzzInputQueue, final BlockingQueue fizzOutputQueue, final BlockingQueue buzzOutputQueue, final long count) { this.fizzBuzzStep = fizzBuzzStep; this.fizzInputQueue = fizzInputQueue; this.buzzInputQueue = buzzInputQueue; this.fizzOutputQueue = fizzOutputQueue; this.buzzOutputQueue = buzzOutputQueue; this.count = count; } public long getFizzBuzzCounter() { return fizzBuzzCounter; } public void reset(final CountDownLatch latch) { fizzBuzzCounter = 0L; sequence = 0L; this.latch = latch; } public void halt() { running = false; } @Override public void run() { running = true; while (true) { try { switch (fizzBuzzStep) { case FIZZ: { Long value = fizzInputQueue.take(); fizzOutputQueue.put(Boolean.valueOf(0 == (value.longValue() % 3))); break; } case BUZZ: { Long value = buzzInputQueue.take(); buzzOutputQueue.put(Boolean.valueOf(0 == (value.longValue() % 5))); break; } case FIZZ_BUZZ: { final boolean fizz = fizzOutputQueue.take().booleanValue(); final boolean buzz = buzzOutputQueue.take().booleanValue(); if (fizz && buzz) { ++fizzBuzzCounter; } break; } } if (null != latch && sequence++ == count) { latch.countDown(); } } catch (InterruptedException ex) { if (!running) { break; } } } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/FizzBuzzStep.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; public enum FizzBuzzStep { FIZZ, BUZZ, FIZZ_BUZZ, } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/FunctionEvent.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventFactory; public final class FunctionEvent { private long operandOne; private long operandTwo; private long stepOneResult; private long stepTwoResult; public long getOperandOne() { return operandOne; } public void setOperandOne(final long operandOne) { this.operandOne = operandOne; } public long getOperandTwo() { return operandTwo; } public void setOperandTwo(final long operandTwo) { this.operandTwo = operandTwo; } public long getStepOneResult() { return stepOneResult; } public void setStepOneResult(final long stepOneResult) { this.stepOneResult = stepOneResult; } public long getStepTwoResult() { return stepTwoResult; } public void setStepTwoResult(final long stepTwoResult) { this.stepTwoResult = stepTwoResult; } public static final EventFactory EVENT_FACTORY = () -> new FunctionEvent(); } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/FunctionEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.util.PaddedLong; import java.util.concurrent.CountDownLatch; public final class FunctionEventHandler implements EventHandler { private final FunctionStep functionStep; private final PaddedLong stepThreeCounter = new PaddedLong(); private long count; private CountDownLatch latch; public FunctionEventHandler(final FunctionStep functionStep) { this.functionStep = functionStep; } public long getStepThreeCounter() { return stepThreeCounter.get(); } public void reset(final CountDownLatch latch, final long expectedCount) { stepThreeCounter.set(0L); this.latch = latch; count = expectedCount; } @Override public void onEvent(final FunctionEvent event, final long sequence, final boolean endOfBatch) throws Exception { switch (functionStep) { case ONE: event.setStepOneResult(event.getOperandOne() + event.getOperandTwo()); break; case TWO: event.setStepTwoResult(event.getStepOneResult() + 3L); break; case THREE: if ((event.getStepTwoResult() & 4L) == 4L) { stepThreeCounter.set(stepThreeCounter.get() + 1L); } break; } if (latch != null && count == sequence) { latch.countDown(); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/FunctionQueueProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; public final class FunctionQueueProcessor implements Runnable { private final FunctionStep functionStep; private final BlockingQueue stepOneQueue; private final BlockingQueue stepTwoQueue; private final BlockingQueue stepThreeQueue; private final long count; private volatile boolean running; private long stepThreeCounter; private long sequence; private CountDownLatch latch; public FunctionQueueProcessor( final FunctionStep functionStep, final BlockingQueue stepOneQueue, final BlockingQueue stepTwoQueue, final BlockingQueue stepThreeQueue, final long count) { this.functionStep = functionStep; this.stepOneQueue = stepOneQueue; this.stepTwoQueue = stepTwoQueue; this.stepThreeQueue = stepThreeQueue; this.count = count; } public long getStepThreeCounter() { return stepThreeCounter; } public void reset(final CountDownLatch latch) { stepThreeCounter = 0L; sequence = 0L; this.latch = latch; } public void halt() { running = false; } @Override public void run() { running = true; while (true) { try { switch (functionStep) { case ONE: { long[] values = stepOneQueue.take(); stepTwoQueue.put(Long.valueOf(values[0] + values[1])); break; } case TWO: { Long value = stepTwoQueue.take(); stepThreeQueue.put(Long.valueOf(value.longValue() + 3)); break; } case THREE: { Long value = stepThreeQueue.take(); long testValue = value.longValue(); if ((testValue & 4L) == 4L) { ++stepThreeCounter; } break; } } if (null != latch && sequence++ == count) { latch.countDown(); } } catch (InterruptedException ex) { if (!running) { break; } } } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/FunctionStep.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; public enum FunctionStep { ONE, TWO, THREE } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/LongArrayEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.util.PaddedLong; import java.util.concurrent.CountDownLatch; public final class LongArrayEventHandler implements EventHandler { private final PaddedLong value = new PaddedLong(); private final PaddedLong batchesProcessed = new PaddedLong(); private long count; private CountDownLatch latch; public long getValue() { return value.get(); } public long getBatchesProcessed() { return batchesProcessed.get(); } public void reset(final CountDownLatch latch, final long expectedCount) { value.set(0L); this.latch = latch; count = expectedCount; batchesProcessed.set(0); } @Override public void onEvent(final long[] event, final long sequence, final boolean endOfBatch) throws Exception { for (int i = 0; i < event.length; i++) { value.set(value.get() + event[i]); } if (--count == 0) { latch.countDown(); } } @Override public void onBatchStart(final long batchSize, final long queueDepth) { batchesProcessed.increment(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/LongArrayPublisher.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.RingBuffer; import java.util.concurrent.CyclicBarrier; public final class LongArrayPublisher implements Runnable { private final CyclicBarrier cyclicBarrier; private final RingBuffer ringBuffer; private final long iterations; private final long arraySize; public LongArrayPublisher( final CyclicBarrier cyclicBarrier, final RingBuffer ringBuffer, final long iterations, final long arraySize) { this.cyclicBarrier = cyclicBarrier; this.ringBuffer = ringBuffer; this.iterations = iterations; this.arraySize = arraySize; } @Override public void run() { try { cyclicBarrier.await(); for (long i = 0; i < iterations; i++) { long sequence = ringBuffer.next(); long[] event = ringBuffer.get(sequence); for (int j = 0; j < arraySize; j++) { event[j] = i + j; } ringBuffer.publish(sequence); } } catch (Exception ex) { throw new RuntimeException(ex); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/MultiBufferBatchEventProcessor.java ================================================ package com.lmax.disruptor.support; import com.lmax.disruptor.AlertException; import com.lmax.disruptor.DataProvider; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; public class MultiBufferBatchEventProcessor implements EventProcessor { private final AtomicBoolean isRunning = new AtomicBoolean(false); private final DataProvider[] providers; private final SequenceBarrier[] barriers; private final EventHandler handler; private final Sequence[] sequences; private long count; public MultiBufferBatchEventProcessor( final DataProvider[] providers, final SequenceBarrier[] barriers, final EventHandler handler) { if (providers.length != barriers.length) { throw new IllegalArgumentException(); } this.providers = providers; this.barriers = barriers; this.handler = handler; this.sequences = new Sequence[providers.length]; for (int i = 0; i < sequences.length; i++) { sequences[i] = new Sequence(-1); } } @Override public void run() { if (!isRunning.compareAndSet(false, true)) { throw new RuntimeException("Already running"); } for (SequenceBarrier barrier : barriers) { barrier.clearAlert(); } final int barrierLength = barriers.length; while (true) { try { for (int i = 0; i < barrierLength; i++) { long available = barriers[i].waitFor(-1); Sequence sequence = sequences[i]; long nextSequence = sequence.get() + 1; for (long l = nextSequence; l <= available; l++) { handler.onEvent(providers[i].get(l), l, nextSequence == available); } sequence.set(available); count += available - nextSequence + 1; } Thread.yield(); } catch (AlertException e) { if (!isRunning()) { break; } } catch (InterruptedException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); break; } } } @Override public Sequence getSequence() { throw new UnsupportedOperationException(); } public long getCount() { return count; } public Sequence[] getSequences() { return sequences; } @Override public void halt() { isRunning.set(false); barriers[0].alert(); } @Override public boolean isRunning() { return isRunning.get(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/Operation.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; public enum Operation { ADDITION { @Override public long op(final long lhs, final long rhs) { return lhs + rhs; } }, SUBTRACTION { @Override public long op(final long lhs, final long rhs) { return lhs - rhs; } }, AND { @Override public long op(final long lhs, final long rhs) { return lhs & rhs; } }; public abstract long op(long lhs, long rhs); } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/PerfTestUtil.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; public final class PerfTestUtil { public static long accumulatedAddition(final long iterations) { long temp = 0L; for (long i = 0L; i < iterations; i++) { temp += i; } return temp; } public static void failIf(final long a, final long b) { if (a == b) { throw new RuntimeException(); } } public static void failIfNot(final long a, final long b) { if (a != b) { throw new RuntimeException(); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueAdditionBatchQueueProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import java.util.ArrayList; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; public final class ValueAdditionBatchQueueProcessor implements Runnable { private volatile boolean running; private long value; private long sequence; private CountDownLatch latch; private final BlockingQueue blockingQueue; private final ArrayList batch = new ArrayList<>(100); private final long count; public ValueAdditionBatchQueueProcessor(final BlockingQueue blockingQueue, final long count) { this.blockingQueue = blockingQueue; this.count = count; } public long getValue() { return value; } public void reset(final CountDownLatch latch) { value = 0L; sequence = 0L; this.latch = latch; } public void halt() { running = false; } @Override public void run() { running = true; while (true) { try { long v = blockingQueue.take(); sequence++; this.value += v; int c = blockingQueue.drainTo(batch, 100); sequence += c; v = 0; for (int i = 0, n = batch.size(); i < n; i++) { v += batch.get(i); } this.value += v; batch.clear(); if (sequence == count) { latch.countDown(); } } catch (InterruptedException ex) { if (!running) { break; } } } } @Override public String toString() { return "ValueAdditionBatchQueueProcessor{" + "value=" + value + ", sequence=" + sequence + ", count=" + count + '}'; } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueAdditionEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.util.PaddedLong; import java.util.concurrent.CountDownLatch; public final class ValueAdditionEventHandler implements EventHandler { private final PaddedLong value = new PaddedLong(); private final PaddedLong batchesProcessed = new PaddedLong(); private long count; private CountDownLatch latch; public long getValue() { return value.get(); } public long getBatchesProcessed() { return batchesProcessed.get(); } public void reset(final CountDownLatch latch, final long expectedCount) { value.set(0L); this.latch = latch; count = expectedCount; batchesProcessed.set(0); } @Override public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) throws Exception { value.set(value.get() + event.getValue()); if (count == sequence) { latch.countDown(); } } @Override public void onBatchStart(final long batchSize, final long queueDepth) { batchesProcessed.increment(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueAdditionQueueBatchProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import java.util.ArrayList; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; public final class ValueAdditionQueueBatchProcessor implements Runnable { private volatile boolean running; private long value; private long sequence; private CountDownLatch latch; private final BlockingQueue blockingQueue; private final ArrayList buffer = new ArrayList<>(); private final long count; public ValueAdditionQueueBatchProcessor(final BlockingQueue blockingQueue, final long count) { this.blockingQueue = blockingQueue; this.count = count; } public long getValue() { return value; } public void reset(final CountDownLatch latch) { value = 0L; sequence = 0L; this.latch = latch; } public void halt() { running = false; } @Override public void run() { running = true; while (true) { try { blockingQueue.drainTo(buffer); if (buffer.isEmpty()) { long value = blockingQueue.take().longValue(); this.value += value; ++sequence; } else { for (Long v : buffer) { this.value += v; } sequence += buffer.size(); buffer.clear(); } if (sequence >= count) { latch.countDown(); } } catch (InterruptedException ex) { if (!running) { break; } } } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueAdditionQueueProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; public final class ValueAdditionQueueProcessor implements Runnable { private volatile boolean running; private long value; private long sequence; private CountDownLatch latch; private final BlockingQueue blockingQueue; private final long count; public ValueAdditionQueueProcessor(final BlockingQueue blockingQueue, final long count) { this.blockingQueue = blockingQueue; this.count = count; } public long getValue() { return value; } public void reset(final CountDownLatch latch) { value = 0L; sequence = 0L; this.latch = latch; } public void halt() { running = false; } @Override public void run() { running = true; while (true) { try { long value = blockingQueue.take().longValue(); this.value += value; if (sequence++ == count) { latch.countDown(); } } catch (InterruptedException ex) { if (!running) { break; } } } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueBatchPublisher.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.RingBuffer; import java.util.concurrent.CyclicBarrier; public final class ValueBatchPublisher implements Runnable { private final CyclicBarrier cyclicBarrier; private final RingBuffer ringBuffer; private final long iterations; private final int batchSize; public ValueBatchPublisher( final CyclicBarrier cyclicBarrier, final RingBuffer ringBuffer, final long iterations, final int batchSize) { this.cyclicBarrier = cyclicBarrier; this.ringBuffer = ringBuffer; this.iterations = iterations; this.batchSize = batchSize; } @Override public void run() { try { cyclicBarrier.await(); for (long i = 0; i < iterations; i += batchSize) { long hi = ringBuffer.next(batchSize); long lo = hi - (batchSize - 1); for (long l = lo; l <= hi; l++) { ValueEvent event = ringBuffer.get(l); event.setValue(l); } ringBuffer.publish(lo, hi); } } catch (Exception ex) { throw new RuntimeException(ex); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueEvent.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventFactory; public final class ValueEvent { private long value; public long getValue() { return value; } public void setValue(final long value) { this.value = value; } public static final EventFactory EVENT_FACTORY = ValueEvent::new; } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueMutationEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.util.PaddedLong; import java.util.concurrent.CountDownLatch; public final class ValueMutationEventHandler implements EventHandler { private final Operation operation; private final PaddedLong value = new PaddedLong(); private final PaddedLong batchesProcessed = new PaddedLong(); private long count; private CountDownLatch latch; public ValueMutationEventHandler(final Operation operation) { this.operation = operation; } public long getValue() { return value.get(); } public long getBatchesProcessed() { return batchesProcessed.get(); } public void reset(final CountDownLatch latch, final long expectedCount) { value.set(0L); this.latch = latch; count = expectedCount; batchesProcessed.set(0); } @Override public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) throws Exception { value.set(operation.op(value.get(), event.getValue())); if (count == sequence) { latch.countDown(); } } @Override public void onBatchStart(final long batchSize, final long queueDepth) { batchesProcessed.increment(); } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueMutationQueueProcessor.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; public final class ValueMutationQueueProcessor implements Runnable { private volatile boolean running; private long value; private long sequence; private CountDownLatch latch; private final BlockingQueue blockingQueue; private final Operation operation; private final long count; public ValueMutationQueueProcessor( final BlockingQueue blockingQueue, final Operation operation, final long count) { this.blockingQueue = blockingQueue; this.operation = operation; this.count = count; } public long getValue() { return value; } public void reset(final CountDownLatch latch) { value = 0L; sequence = 0L; this.latch = latch; } public void halt() { running = false; } @Override public void run() { running = true; while (true) { try { long value = blockingQueue.take().longValue(); this.value = operation.op(this.value, value); if (sequence++ == count) { latch.countDown(); } } catch (InterruptedException ex) { if (!running) { break; } } } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValuePublisher.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.RingBuffer; import java.util.concurrent.CyclicBarrier; public final class ValuePublisher implements Runnable { private final CyclicBarrier cyclicBarrier; private final RingBuffer ringBuffer; private final long iterations; public ValuePublisher( final CyclicBarrier cyclicBarrier, final RingBuffer ringBuffer, final long iterations) { this.cyclicBarrier = cyclicBarrier; this.ringBuffer = ringBuffer; this.iterations = iterations; } @Override public void run() { try { cyclicBarrier.await(); for (long i = 0; i < iterations; i++) { long sequence = ringBuffer.next(); ValueEvent event = ringBuffer.get(sequence); event.setValue(i); ringBuffer.publish(sequence); } } catch (Exception ex) { throw new RuntimeException(ex); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/support/ValueQueuePublisher.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CyclicBarrier; public final class ValueQueuePublisher implements Runnable { private final CyclicBarrier cyclicBarrier; private final BlockingQueue blockingQueue; private final long iterations; public ValueQueuePublisher( final CyclicBarrier cyclicBarrier, final BlockingQueue blockingQueue, final long iterations) { this.cyclicBarrier = cyclicBarrier; this.blockingQueue = blockingQueue; this.iterations = iterations; } @Override public void run() { try { cyclicBarrier.await(); for (long i = 0; i < iterations; i++) { blockingQueue.put(Long.valueOf(i)); } } catch (Exception ex) { throw new RuntimeException(ex); } } } ================================================ FILE: src/perftest/java/com/lmax/disruptor/translator/OneToOneTranslatorThroughputTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.translator; import com.lmax.disruptor.AbstractPerfTestDisruptor; import com.lmax.disruptor.EventTranslatorOneArg; import com.lmax.disruptor.PerfTestContext; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.support.PerfTestUtil; import com.lmax.disruptor.support.ValueAdditionEventHandler; import com.lmax.disruptor.support.ValueEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import com.lmax.disruptor.util.MutableLong; import java.util.concurrent.CountDownLatch; import static com.lmax.disruptor.support.PerfTestUtil.failIfNot; /** *
 * UniCast a series of items between 1 publisher and 1 event processor using the EventTranslator API
 *
 * +----+    +-----+
 * | P1 |--->| EP1 |
 * +----+    +-----+
 *
 * Disruptor:
 * ==========
 *              track to prevent wrap
 *              +------------------+
 *              |                  |
 *              |                  v
 * +----+    +====+    +====+   +-----+
 * | P1 |--->| RB |<---| SB |   | EP1 |
 * +----+    +====+    +====+   +-----+
 *      claim      get    ^        |
 *                        |        |
 *                        +--------+
 *                          waitFor
 *
 * P1  - Publisher 1
 * RB  - RingBuffer
 * SB  - SequenceBarrier
 * EP1 - EventProcessor 1
 *
 * 
*/ public final class OneToOneTranslatorThroughputTest extends AbstractPerfTestDisruptor { private static final int BUFFER_SIZE = 1024 * 64; private static final long ITERATIONS = 1000L * 1000L * 100L; private final long expectedResult = PerfTestUtil.accumulatedAddition(ITERATIONS); private final ValueAdditionEventHandler handler = new ValueAdditionEventHandler(); private final RingBuffer ringBuffer; private final MutableLong value = new MutableLong(0); /////////////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("unchecked") public OneToOneTranslatorThroughputTest() { Disruptor disruptor = new Disruptor<>( ValueEvent.EVENT_FACTORY, BUFFER_SIZE, DaemonThreadFactory.INSTANCE, ProducerType.SINGLE, new YieldingWaitStrategy()); disruptor.handleEventsWith(handler); this.ringBuffer = disruptor.start(); } /////////////////////////////////////////////////////////////////////////////////////////////// @Override protected int getRequiredProcessorCount() { return 2; } @Override protected PerfTestContext runDisruptorPass() throws InterruptedException { PerfTestContext perfTestContext = new PerfTestContext(); MutableLong value = this.value; final CountDownLatch latch = new CountDownLatch(1); long expectedCount = ringBuffer.getMinimumGatingSequence() + ITERATIONS; handler.reset(latch, expectedCount); long start = System.currentTimeMillis(); final RingBuffer rb = ringBuffer; for (long l = 0; l < ITERATIONS; l++) { value.set(l); rb.publishEvent(Translator.INSTANCE, value); } latch.await(); perfTestContext.setDisruptorOps((ITERATIONS * 1000L) / (System.currentTimeMillis() - start)); perfTestContext.setBatchData(handler.getBatchesProcessed(), ITERATIONS); waitForEventProcessorSequence(expectedCount); failIfNot(expectedResult, handler.getValue()); return perfTestContext; } private static class Translator implements EventTranslatorOneArg { private static final Translator INSTANCE = new Translator(); @Override public void translateTo(final ValueEvent event, final long sequence, final MutableLong arg0) { event.setValue(arg0.get()); } } private void waitForEventProcessorSequence(final long expectedCount) throws InterruptedException { while (ringBuffer.getMinimumGatingSequence() != expectedCount) { Thread.sleep(1); } } public static void main(final String[] args) throws Exception { OneToOneTranslatorThroughputTest test = new OneToOneTranslatorThroughputTest(); test.testImplementations(); } } ================================================ FILE: src/test/java/com/lmax/disruptor/AggregateEventHandlerTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.DummyEventHandler; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @SuppressWarnings("unchecked") public final class AggregateEventHandlerTest { private final DummyEventHandler eh1 = new DummyEventHandler<>(); private final DummyEventHandler eh2 = new DummyEventHandler<>(); private final DummyEventHandler eh3 = new DummyEventHandler<>(); @Test public void shouldCallOnEventInSequence() throws Exception { final int[] event = {7}; final long sequence = 3L; final boolean endOfBatch = true; final AggregateEventHandler aggregateEventHandler = new AggregateEventHandler<>(eh1, eh2, eh3); aggregateEventHandler.onEvent(event, sequence, endOfBatch); assertLastEvent(event, sequence, eh1, eh2, eh3); } @Test public void shouldCallOnStartInSequence() throws Exception { final AggregateEventHandler aggregateEventHandler = new AggregateEventHandler<>(eh1, eh2, eh3); aggregateEventHandler.onStart(); assertStartCalls(1, eh1, eh2, eh3); } @Test public void shouldCallOnShutdownInSequence() throws Exception { final AggregateEventHandler aggregateEventHandler = new AggregateEventHandler<>(eh1, eh2, eh3); aggregateEventHandler.onShutdown(); assertShutdownCalls(1, eh1, eh2, eh3); } @Test public void shouldHandleEmptyListOfEventHandlers() throws Exception { final AggregateEventHandler aggregateEventHandler = new AggregateEventHandler<>(); aggregateEventHandler.onEvent(new int[]{7}, 0L, true); aggregateEventHandler.onStart(); aggregateEventHandler.onShutdown(); } private static void assertLastEvent(final int[] event, final long sequence, final DummyEventHandler... eh1) { for (DummyEventHandler eh : eh1) { assertThat(eh.lastEvent, is(event)); assertThat(eh.lastSequence, is(sequence)); } } private static void assertStartCalls(final int startCalls, final DummyEventHandler... handlers) { for (DummyEventHandler handler : handlers) { assertThat(handler.startCalls, is(startCalls)); } } private static void assertShutdownCalls(final int startCalls, final DummyEventHandler... handlers) { for (DummyEventHandler handler : handlers) { assertThat(handler.shutdownCalls, is(startCalls)); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/BatchEventProcessorTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.StubEvent; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static com.lmax.disruptor.RingBuffer.createMultiProducer; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public final class BatchEventProcessorTest { private final RingBuffer ringBuffer = createMultiProducer(StubEvent.EVENT_FACTORY, 16); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); @Test public void shouldThrowExceptionOnSettingNullExceptionHandler() { assertThrows(NullPointerException.class, () -> { final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build( ringBuffer, sequenceBarrier, new ExceptionEventHandler()); batchEventProcessor.setExceptionHandler(null); }); } @Test public void shouldCallMethodsInLifecycleOrderForBatch() throws Exception { CountDownLatch eventLatch = new CountDownLatch(3); LatchEventHandler eventHandler = new LatchEventHandler(eventLatch); final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build( ringBuffer, sequenceBarrier, eventHandler); ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); ringBuffer.publish(ringBuffer.next()); ringBuffer.publish(ringBuffer.next()); ringBuffer.publish(ringBuffer.next()); Thread thread = new Thread(batchEventProcessor); thread.start(); assertTrue(eventLatch.await(2, TimeUnit.SECONDS)); batchEventProcessor.halt(); thread.join(); } @Test public void shouldCallExceptionHandlerOnUncaughtException() throws Exception { CountDownLatch exceptionLatch = new CountDownLatch(1); LatchExceptionHandler latchExceptionHandler = new LatchExceptionHandler(exceptionLatch); final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build( ringBuffer, sequenceBarrier, new ExceptionEventHandler()); ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); batchEventProcessor.setExceptionHandler(latchExceptionHandler); Thread thread = new Thread(batchEventProcessor); thread.start(); ringBuffer.publish(ringBuffer.next()); assertTrue(exceptionLatch.await(2, TimeUnit.SECONDS)); batchEventProcessor.halt(); thread.join(); } private static class LatchEventHandler implements EventHandler { private final CountDownLatch latch; LatchEventHandler(final CountDownLatch latch) { this.latch = latch; } @Override public void onEvent(final StubEvent event, final long sequence, final boolean endOfBatch) throws Exception { latch.countDown(); } } private static class LatchExceptionHandler implements ExceptionHandler { private final CountDownLatch latch; LatchExceptionHandler(final CountDownLatch latch) { this.latch = latch; } @Override public void handleEventException(final Throwable ex, final long sequence, final StubEvent event) { latch.countDown(); } @Override public void handleOnStartException(final Throwable ex) { } @Override public void handleOnShutdownException(final Throwable ex) { } } private static class ExceptionEventHandler implements EventHandler { @Override public void onEvent(final StubEvent event, final long sequence, final boolean endOfBatch) throws Exception { throw new NullPointerException(null); } } @Test public void reportAccurateBatchSizesAtBatchStartTime() throws Exception { final List batchSizes = new ArrayList<>(); final CountDownLatch eventLatch = new CountDownLatch(6); final class LoopbackEventHandler implements EventHandler { @Override public void onBatchStart(final long batchSize, final long queueDepth) { batchSizes.add(batchSize); } @Override public void onEvent(final StubEvent event, final long sequence, final boolean endOfBatch) throws Exception { if (!endOfBatch) { ringBuffer.publish(ringBuffer.next()); } eventLatch.countDown(); } } final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build( ringBuffer, sequenceBarrier, new LoopbackEventHandler()); ringBuffer.publish(ringBuffer.next()); ringBuffer.publish(ringBuffer.next()); ringBuffer.publish(ringBuffer.next()); Thread thread = new Thread(batchEventProcessor); thread.start(); eventLatch.await(); batchEventProcessor.halt(); thread.join(); assertEquals(Arrays.asList(3L, 2L, 1L), batchSizes); } @Test public void shouldAlwaysHalt() throws InterruptedException { WaitStrategy waitStrategy = new BusySpinWaitStrategy(); final SingleProducerSequencer sequencer = new SingleProducerSequencer(8, waitStrategy); final ProcessingSequenceBarrier barrier = new ProcessingSequenceBarrier( sequencer, waitStrategy, new Sequence(-1), new Sequence[0]); DataProvider dp = sequence -> null; final LatchLifeCycleHandler h1 = new LatchLifeCycleHandler(); final BatchEventProcessor p1 = new BatchEventProcessorBuilder().build(dp, barrier, h1); Thread t1 = new Thread(p1); p1.halt(); t1.start(); assertTrue(h1.awaitStart(2, TimeUnit.SECONDS)); assertTrue(h1.awaitStop(2, TimeUnit.SECONDS)); for (int i = 0; i < 1000; i++) { final LatchLifeCycleHandler h2 = new LatchLifeCycleHandler(); final BatchEventProcessor p2 = new BatchEventProcessorBuilder().build(dp, barrier, h2); Thread t2 = new Thread(p2); t2.start(); p2.halt(); assertTrue(h2.awaitStart(2, TimeUnit.SECONDS)); assertTrue(h2.awaitStop(2, TimeUnit.SECONDS)); } for (int i = 0; i < 1000; i++) { final LatchLifeCycleHandler h2 = new LatchLifeCycleHandler(); final BatchEventProcessor p2 = new BatchEventProcessorBuilder().build(dp, barrier, h2); Thread t2 = new Thread(p2); t2.start(); Thread.yield(); p2.halt(); assertTrue(h2.awaitStart(2, TimeUnit.SECONDS)); assertTrue(h2.awaitStop(2, TimeUnit.SECONDS)); } } private static class LatchLifeCycleHandler implements EventHandler { private final CountDownLatch startLatch = new CountDownLatch(1); private final CountDownLatch stopLatch = new CountDownLatch(1); @Override public void onEvent(final Object event, final long sequence, final boolean endOfBatch) throws Exception { } @Override public void onStart() { startLatch.countDown(); } @Override public void onShutdown() { stopLatch.countDown(); } public boolean awaitStart(final long time, final TimeUnit unit) throws InterruptedException { return startLatch.await(time, unit); } public boolean awaitStop(final long time, final TimeUnit unit) throws InterruptedException { return stopLatch.await(time, unit); } } @Test public void shouldNotPassZeroSizeToBatchStartAware() throws Exception { BatchAwareEventHandler eventHandler = new BatchAwareEventHandler(); final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build( ringBuffer, new DelegatingSequenceBarrier(this.sequenceBarrier), eventHandler); ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); Thread thread = new Thread(batchEventProcessor); thread.start(); for (int i = 0; i < 3; i++) { final long sequence = ringBuffer.next(); Thread.sleep(100); ringBuffer.publish(sequence); } batchEventProcessor.halt(); thread.join(); assertThat(eventHandler.batchSizeToCountMap.size(), not(0)); assertThat(eventHandler.batchSizeToCountMap.get(0L), nullValue()); } private static class DelegatingSequenceBarrier implements SequenceBarrier { private SequenceBarrier delegate; private boolean suppress = true; DelegatingSequenceBarrier(final SequenceBarrier delegate) { this.delegate = delegate; } @Override public long waitFor(final long sequence) throws AlertException, InterruptedException, TimeoutException { long result = suppress ? sequence - 1 : delegate.waitFor(sequence); suppress = !suppress; return result; } @Override public long getCursor() { return delegate.getCursor(); } @Override public boolean isAlerted() { return delegate.isAlerted(); } @Override public void alert() { delegate.alert(); } @Override public void clearAlert() { delegate.clearAlert(); } @Override public void checkAlert() throws AlertException { delegate.checkAlert(); } } private static class BatchAwareEventHandler implements EventHandler { final Map batchSizeToCountMap = new HashMap<>(); @Override public void onEvent(final StubEvent event, final long sequence, final boolean endOfBatch) throws Exception { } @Override public void onBatchStart(final long batchSize, final long queueDepth) { final Integer currentCount = batchSizeToCountMap.get(batchSize); final int nextCount = null == currentCount ? 1 : currentCount + 1; batchSizeToCountMap.put(batchSize, nextCount); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/BatchingTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.support.LongEvent; import com.lmax.disruptor.util.DaemonThreadFactory; import org.hamcrest.CoreMatchers; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; public class BatchingTest { public static Stream generateData() { return Stream.of(arguments(ProducerType.MULTI), arguments(ProducerType.SINGLE)); } private static class ParallelEventHandler implements EventHandler { private final long mask; private final long ordinal; private final int batchSize = 10; private long eventCount; private long batchCount; private long publishedValue; private long tempValue; private volatile long processed; ParallelEventHandler(final long mask, final long ordinal) { this.mask = mask; this.ordinal = ordinal; } @Override public void onEvent(final LongEvent event, final long sequence, final boolean endOfBatch) throws Exception { if ((sequence & mask) == ordinal) { eventCount++; tempValue = event.get(); } if (endOfBatch || ++batchCount >= batchSize) { publishedValue = tempValue; batchCount = 0; } else { LockSupport.parkNanos(1); } processed = sequence; } } @SuppressWarnings("unchecked") @ParameterizedTest @MethodSource("generateData") public void shouldBatch(final ProducerType producerType) throws Exception { Disruptor d = new Disruptor<>( LongEvent.FACTORY, 2048, DaemonThreadFactory.INSTANCE, producerType, new SleepingWaitStrategy()); ParallelEventHandler handler1 = new ParallelEventHandler(1, 0); ParallelEventHandler handler2 = new ParallelEventHandler(1, 1); d.handleEventsWith(handler1, handler2); RingBuffer buffer = d.start(); EventTranslator translator = (event, sequence) -> event.set(sequence); int eventCount = 10000; for (int i = 0; i < eventCount; i++) { buffer.publishEvent(translator); } while (handler1.processed != eventCount - 1 || handler2.processed != eventCount - 1) { Thread.sleep(1); } assertThat(handler1.publishedValue, CoreMatchers.is((long) eventCount - 2)); assertThat(handler1.eventCount, CoreMatchers.is((long) eventCount / 2)); assertThat(handler2.publishedValue, CoreMatchers.is((long) eventCount - 1)); assertThat(handler2.eventCount, CoreMatchers.is((long) eventCount / 2)); } } ================================================ FILE: src/test/java/com/lmax/disruptor/BusySpinWaitStrategyTest.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import org.junit.jupiter.api.Test; import static com.lmax.disruptor.support.WaitStrategyTestUtil.assertWaitForWithDelayOf; public class BusySpinWaitStrategyTest { @Test public void shouldWaitForValue() throws Exception { assertWaitForWithDelayOf(50, new BusySpinWaitStrategy()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/DisruptorStressTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.util.DaemonThreadFactory; import org.junit.jupiter.api.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.LockSupport; import static java.lang.Math.max; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; public class DisruptorStressTest { private final ExecutorService executor = Executors.newCachedThreadPool(); @Test public void shouldHandleLotsOfThreads() throws Exception { Disruptor disruptor = new Disruptor<>( TestEvent.FACTORY, 1 << 16, DaemonThreadFactory.INSTANCE, ProducerType.MULTI, new BusySpinWaitStrategy()); RingBuffer ringBuffer = disruptor.getRingBuffer(); disruptor.setDefaultExceptionHandler(new FatalExceptionHandler()); int threads = max(1, Runtime.getRuntime().availableProcessors() / 2); int iterations = 200000; int publisherCount = threads; int handlerCount = threads; CyclicBarrier barrier = new CyclicBarrier(publisherCount); CountDownLatch latch = new CountDownLatch(publisherCount); TestEventHandler[] handlers = initialise(disruptor, new TestEventHandler[handlerCount]); Publisher[] publishers = initialise(new Publisher[publisherCount], ringBuffer, iterations, barrier, latch); disruptor.start(); for (Publisher publisher : publishers) { executor.execute(publisher); } latch.await(); while (ringBuffer.getCursor() < (iterations - 1)) { LockSupport.parkNanos(1); } disruptor.shutdown(); for (Publisher publisher : publishers) { assertThat(publisher.failed, is(false)); } for (TestEventHandler handler : handlers) { assertThat(handler.messagesSeen, is(not(0))); assertThat(handler.failureCount, is(0)); } } private Publisher[] initialise( final Publisher[] publishers, final RingBuffer buffer, final int messageCount, final CyclicBarrier barrier, final CountDownLatch latch) { for (int i = 0; i < publishers.length; i++) { publishers[i] = new Publisher(buffer, messageCount, barrier, latch); } return publishers; } @SuppressWarnings("unchecked") private TestEventHandler[] initialise(final Disruptor disruptor, final TestEventHandler[] testEventHandlers) { for (int i = 0; i < testEventHandlers.length; i++) { TestEventHandler handler = new TestEventHandler(); disruptor.handleEventsWith(handler); testEventHandlers[i] = handler; } return testEventHandlers; } private static class TestEventHandler implements EventHandler { public int failureCount = 0; public int messagesSeen = 0; TestEventHandler() { } @Override public void onEvent(final TestEvent event, final long sequence, final boolean endOfBatch) throws Exception { if (event.sequence != sequence || event.a != sequence + 13 || event.b != sequence - 7 || !("wibble-" + sequence).equals(event.s)) { failureCount++; } messagesSeen++; } } private static class Publisher implements Runnable { private final RingBuffer ringBuffer; private final CyclicBarrier barrier; private final int iterations; private final CountDownLatch shutdownLatch; public boolean failed = false; Publisher( final RingBuffer ringBuffer, final int iterations, final CyclicBarrier barrier, final CountDownLatch shutdownLatch) { this.ringBuffer = ringBuffer; this.barrier = barrier; this.iterations = iterations; this.shutdownLatch = shutdownLatch; } @Override public void run() { try { barrier.await(); int i = iterations; while (--i != -1) { long next = ringBuffer.next(); TestEvent testEvent = ringBuffer.get(next); testEvent.sequence = next; testEvent.a = next + 13; testEvent.b = next - 7; testEvent.s = "wibble-" + next; ringBuffer.publish(next); } } catch (Exception e) { failed = true; } finally { shutdownLatch.countDown(); } } } private static class TestEvent { public long sequence; public long a; public long b; public String s; public static final EventFactory FACTORY = () -> new TestEvent(); } } ================================================ FILE: src/test/java/com/lmax/disruptor/EventPollerTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.EventPoller.PollState; import org.junit.jupiter.api.Test; import java.util.ArrayList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; public class EventPollerTest { @Test @SuppressWarnings("unchecked") public void shouldPollForEvents() throws Exception { final Sequence gatingSequence = new Sequence(); final SingleProducerSequencer sequencer = new SingleProducerSequencer(16, new BusySpinWaitStrategy()); final EventPoller.Handler handler = (event, sequence, endOfBatch) -> false; final Object[] data = new Object[16]; final DataProvider provider = sequence -> data[(int) sequence]; final EventPoller poller = sequencer.newPoller(provider, gatingSequence); final Object event = new Object(); data[0] = event; assertThat(poller.poll(handler), is(PollState.IDLE)); // Publish Event. sequencer.publish(sequencer.next()); assertThat(poller.poll(handler), is(PollState.GATING)); gatingSequence.incrementAndGet(); assertThat(poller.poll(handler), is(PollState.PROCESSING)); } @Test public void shouldSuccessfullyPollWhenBufferIsFull() throws Exception { final ArrayList events = new ArrayList<>(); final EventPoller.Handler handler = (event, sequence, endOfBatch) -> { events.add(event); return !endOfBatch; }; EventFactory factory = () -> new byte[1]; final RingBuffer ringBuffer = RingBuffer.createMultiProducer(factory, 4, new SleepingWaitStrategy()); final EventPoller poller = ringBuffer.newPoller(); ringBuffer.addGatingSequences(poller.getSequence()); int count = 4; for (byte i = 1; i <= count; ++i) { long next = ringBuffer.next(); ringBuffer.get(next)[0] = i; ringBuffer.publish(next); } // think of another thread poller.poll(handler); assertThat(events.size(), is(4)); } } ================================================ FILE: src/test/java/com/lmax/disruptor/EventPublisherTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.LongEvent; import org.junit.jupiter.api.Test; import static com.lmax.disruptor.RingBuffer.createMultiProducer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; public class EventPublisherTest implements EventTranslator { private static final int BUFFER_SIZE = 32; private RingBuffer ringBuffer = createMultiProducer(LongEvent.FACTORY, BUFFER_SIZE); @Test public void shouldPublishEvent() { ringBuffer.addGatingSequences(new NoOpEventProcessor(ringBuffer).getSequence()); ringBuffer.publishEvent(this); ringBuffer.publishEvent(this); assertThat(Long.valueOf(ringBuffer.get(0).get()), is(Long.valueOf(0 + 29L))); assertThat(Long.valueOf(ringBuffer.get(1).get()), is(Long.valueOf(1 + 29L))); } @Test public void shouldTryPublishEvent() throws Exception { ringBuffer.addGatingSequences(new Sequence()); for (int i = 0; i < BUFFER_SIZE; i++) { assertThat(ringBuffer.tryPublishEvent(this), is(true)); } for (int i = 0; i < BUFFER_SIZE; i++) { assertThat(Long.valueOf(ringBuffer.get(i).get()), is(Long.valueOf(i + 29L))); } assertThat(ringBuffer.tryPublishEvent(this), is(false)); } @Override public void translateTo(final LongEvent event, final long sequence) { event.set(sequence + 29); } } ================================================ FILE: src/test/java/com/lmax/disruptor/EventTranslatorTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.StubEvent; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public final class EventTranslatorTest { private static final String TEST_VALUE = "Wibble"; @Test public void shouldTranslateOtherDataIntoAnEvent() { StubEvent event = StubEvent.EVENT_FACTORY.newInstance(); EventTranslator eventTranslator = new ExampleEventTranslator(TEST_VALUE); eventTranslator.translateTo(event, 0); assertEquals(TEST_VALUE, event.getTestString()); } public static final class ExampleEventTranslator implements EventTranslator { private final String testValue; public ExampleEventTranslator(final String testValue) { this.testValue = testValue; } @Override public void translateTo(final StubEvent event, final long sequence) { event.setTestString(testValue); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/FatalExceptionHandlerTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.TestEvent; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public final class FatalExceptionHandlerTest { @Test public void shouldHandleFatalException() { final Exception causeException = new Exception(); final TestEvent event = new TestEvent(); ExceptionHandler exceptionHandler = new FatalExceptionHandler(); Throwable ex = assertThrows(RuntimeException.class, () -> exceptionHandler.handleEventException(causeException, 0L, event)); assertEquals(causeException, ex.getCause()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/FixedSequenceGroupTest.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; public class FixedSequenceGroupTest { @Test public void shouldReturnMinimumOf2Sequences() throws Exception { Sequence sequence1 = new Sequence(34); Sequence sequnece2 = new Sequence(47); Sequence group = new FixedSequenceGroup(new Sequence[]{sequence1, sequnece2}); assertThat(group.get(), is(34L)); sequence1.set(35); assertThat(group.get(), is(35L)); sequence1.set(48); assertThat(group.get(), is(47L)); } } ================================================ FILE: src/test/java/com/lmax/disruptor/IgnoreExceptionHandlerTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.TestEvent; import org.junit.jupiter.api.Test; public final class IgnoreExceptionHandlerTest { @Test public void shouldHandleAndIgnoreException() { final Exception ex = new Exception(); final TestEvent event = new TestEvent(); ExceptionHandler exceptionHandler = new IgnoreExceptionHandler(); exceptionHandler.handleEventException(ex, 0L, event); } } ================================================ FILE: src/test/java/com/lmax/disruptor/LifecycleAwareTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.StubEvent; import org.junit.jupiter.api.Test; import java.util.concurrent.CountDownLatch; import static com.lmax.disruptor.RingBuffer.createMultiProducer; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; public final class LifecycleAwareTest { private final CountDownLatch startLatch = new CountDownLatch(1); private final CountDownLatch shutdownLatch = new CountDownLatch(1); private final RingBuffer ringBuffer = createMultiProducer(StubEvent.EVENT_FACTORY, 16); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final LifecycleAwareEventHandler handler = new LifecycleAwareEventHandler(); private final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handler); @Test public void shouldNotifyOfBatchProcessorLifecycle() throws Exception { new Thread(batchEventProcessor).start(); startLatch.await(); batchEventProcessor.halt(); shutdownLatch.await(); assertThat(Integer.valueOf(handler.startCounter), is(Integer.valueOf(1))); assertThat(Integer.valueOf(handler.shutdownCounter), is(Integer.valueOf(1))); } private final class LifecycleAwareEventHandler implements EventHandler { private int startCounter = 0; private int shutdownCounter = 0; @Override public void onEvent(final StubEvent event, final long sequence, final boolean endOfBatch) throws Exception { } @Override public void onStart() { ++startCounter; startLatch.countDown(); } @Override public void onShutdown() { ++shutdownCounter; shutdownLatch.countDown(); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/LiteTimeoutBlockingWaitStrategyTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.support.DummySequenceBarrier; import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class LiteTimeoutBlockingWaitStrategyTest { @Test public void shouldTimeoutWaitFor() { final SequenceBarrier sequenceBarrier = new DummySequenceBarrier(); long theTimeout = 500; LiteTimeoutBlockingWaitStrategy waitStrategy = new LiteTimeoutBlockingWaitStrategy(theTimeout, TimeUnit.MILLISECONDS); Sequence cursor = new Sequence(5); long t0 = System.currentTimeMillis(); assertThrows(TimeoutException.class, () -> waitStrategy.waitFor(6, cursor, cursor, sequenceBarrier)); long t1 = System.currentTimeMillis(); long timeWaiting = t1 - t0; assertTrue(timeWaiting >= theTimeout); } } ================================================ FILE: src/test/java/com/lmax/disruptor/MaxBatchSizeEventProcessorTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.StubEvent; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import static com.lmax.disruptor.RingBuffer.createSingleProducer; import static org.junit.jupiter.api.Assertions.assertEquals; public final class MaxBatchSizeEventProcessorTest { public static final int MAX_BATCH_SIZE = 3; public static final int PUBLISH_COUNT = 5; private final RingBuffer ringBuffer = createSingleProducer(StubEvent.EVENT_FACTORY, 16); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private CountDownLatch countDownLatch; private BatchEventProcessor batchEventProcessor; private Thread thread; private BatchLimitRecordingHandler eventHandler; @BeforeEach void setUp() { countDownLatch = new CountDownLatch(PUBLISH_COUNT); eventHandler = new BatchLimitRecordingHandler(countDownLatch); batchEventProcessor = new BatchEventProcessorBuilder() .setMaxBatchSize(MAX_BATCH_SIZE) .build(ringBuffer, this.sequenceBarrier, eventHandler); ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); thread = new Thread(batchEventProcessor); thread.start(); } @Test public void shouldLimitTheBatchToConfiguredMaxBatchSize() throws Exception { publishEvents(); assertEquals(eventHandler.batchedSequences, Arrays.asList(Arrays.asList(0L, 1L, 2L), Arrays.asList(3L, 4L))); } @Test public void shouldAnnounceBatchSizeAndQueueDepthAtTheStartOfBatch() throws Exception { publishEvents(); assertEquals(eventHandler.announcedBatchSizes, Arrays.asList(3L, 2L)); assertEquals(eventHandler.announcedQueueDepths, Arrays.asList(5L, 2L)); } @AfterEach void tearDown() throws InterruptedException { batchEventProcessor.halt(); thread.join(); } private void publishEvents() throws InterruptedException { long sequence = 0; for (int i = 0; i < PUBLISH_COUNT; i++) { sequence = ringBuffer.next(); } ringBuffer.publish(sequence); //Wait for consumer to process all events countDownLatch.await(); } private static class BatchLimitRecordingHandler implements EventHandler { public final List> batchedSequences = new ArrayList<>(); private List currentSequences; private final CountDownLatch countDownLatch; private final List announcedBatchSizes = new ArrayList<>(); private final List announcedQueueDepths = new ArrayList<>(); BatchLimitRecordingHandler(final CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void onEvent(final StubEvent event, final long sequence, final boolean endOfBatch) throws Exception { currentSequences.add(sequence); if (endOfBatch) { batchedSequences.add(currentSequences); currentSequences = null; } countDownLatch.countDown(); } @Override public void onBatchStart(final long batchSize, final long queueDepth) { currentSequences = new ArrayList<>(); announcedBatchSizes.add(batchSize); announcedQueueDepths.add(queueDepth); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/MultiProducerSequencerTest.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; public class MultiProducerSequencerTest { private final Sequencer publisher = new MultiProducerSequencer(1024, new BlockingWaitStrategy()); @Test public void shouldOnlyAllowMessagesToBeAvailableIfSpecificallyPublished() throws Exception { publisher.publish(3); publisher.publish(5); assertThat(publisher.isAvailable(0), is(false)); assertThat(publisher.isAvailable(1), is(false)); assertThat(publisher.isAvailable(2), is(false)); assertThat(publisher.isAvailable(3), is(true)); assertThat(publisher.isAvailable(4), is(false)); assertThat(publisher.isAvailable(5), is(true)); assertThat(publisher.isAvailable(6), is(false)); } } ================================================ FILE: src/test/java/com/lmax/disruptor/PhasedBackoffWaitStrategyTest.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import org.junit.jupiter.api.Test; import static com.lmax.disruptor.support.WaitStrategyTestUtil.assertWaitForWithDelayOf; import static java.util.concurrent.TimeUnit.MILLISECONDS; public class PhasedBackoffWaitStrategyTest { @Test public void shouldHandleImmediateSequenceChange() throws Exception { assertWaitForWithDelayOf(0, PhasedBackoffWaitStrategy.withLock(1, 1, MILLISECONDS)); assertWaitForWithDelayOf(0, PhasedBackoffWaitStrategy.withSleep(1, 1, MILLISECONDS)); } @Test public void shouldHandleSequenceChangeWithOneMillisecondDelay() throws Exception { assertWaitForWithDelayOf(1, PhasedBackoffWaitStrategy.withLock(1, 1, MILLISECONDS)); assertWaitForWithDelayOf(1, PhasedBackoffWaitStrategy.withSleep(1, 1, MILLISECONDS)); } @Test public void shouldHandleSequenceChangeWithTwoMillisecondDelay() throws Exception { assertWaitForWithDelayOf(2, PhasedBackoffWaitStrategy.withLock(1, 1, MILLISECONDS)); assertWaitForWithDelayOf(2, PhasedBackoffWaitStrategy.withSleep(1, 1, MILLISECONDS)); } @Test public void shouldHandleSequenceChangeWithTenMillisecondDelay() throws Exception { assertWaitForWithDelayOf(10, PhasedBackoffWaitStrategy.withLock(1, 1, MILLISECONDS)); assertWaitForWithDelayOf(10, PhasedBackoffWaitStrategy.withSleep(1, 1, MILLISECONDS)); } } ================================================ FILE: src/test/java/com/lmax/disruptor/RewindBatchEventProcessorTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.dsl.stubs.StubExceptionHandler; import com.lmax.disruptor.support.LongEvent; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class RewindBatchEventProcessorTest { private static final int BUFFER_SIZE = 2048; private RingBuffer ringBuffer; private final List values = new ArrayList<>(); @BeforeEach public void setUp() { ringBuffer = RingBuffer.createMultiProducer(LongEvent.FACTORY, BUFFER_SIZE); } @Test public void shouldRewindOnFirstEventOfBatchSizeOfOne() { fill(ringBuffer, 1); final TestEventHandler eventHandler = new TestEventHandler(values, List.of(rewind(0, 1)), 0, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence(event(0, 0))); } @Test public void shouldRewindOnFirstEventOfBatch() { int ringBufferEntries = 10; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler( values, singletonList(rewind(0, 1)), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, lastSequenceNumber))); } @Test public void shouldRewindOnEventInMiddleOfBatch() { int ringBufferEntries = 10; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler( values, singletonList(rewind(8, 1)), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 7), event(0, lastSequenceNumber))); } @Test public void shouldRewindOnLastEventOfBatch() { int ringBufferEntries = 10; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler( values, singletonList(rewind(lastSequenceNumber, 1)), lastSequenceNumber, -1 ); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 8), event(0, lastSequenceNumber))); } @Test public void shouldRunBatchCompleteOnLastEventOfBatch() { int ringBufferEntries = 10; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(4, 1)), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 3), event(0, lastSequenceNumber))); } @Test public void shouldRunBatchCompleteOnLastEventOfBatchOfOne() { fill(ringBuffer, 1); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(0, 1)), 0, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 0))); } @Test public void shouldRewindMultipleTimes() { int ringBufferEntries = 10; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(8, 3)), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 7), event(0, 7), event(0, 7), event(0, lastSequenceNumber))); } @Test public void shouldRewindMultipleTimesOnLastEventInBatch() { int ringBufferEntries = 10; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(lastSequenceNumber, 3)), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 8), event(0, 8), event(0, 8), event(0, lastSequenceNumber))); } @Test public void shouldRewindMultipleTimesInSameBatch() { int ringBufferEntries = 10; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, asList(rewind(5, 3), rewind(7, 3)), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 4), event(0, 4), event(0, 4), event(0, 6), event(0, 6), event(0, 6), event(0, lastSequenceNumber))); } @Test public void shouldRewindMultipleTimesOnBatchOfOne() { fill(ringBuffer, 1); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(0, 3)), 0, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); // nothing is actually written as first event is rewound assertThat(values, containsExactSequence( event(0, 0))); } @Test public void shouldFallOverWhenNonRewindableExceptionIsThrown() { int ringBufferEntries = 10; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, emptyList(), lastSequenceNumber, 8); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); AtomicReference exceptionHandled = new AtomicReference<>(); eventProcessor.setExceptionHandler(new StubExceptionHandler(exceptionHandled)); eventProcessor.run(); assertEquals("not rewindable", exceptionHandled.get().getMessage()); } @Test public void shouldProcessUpToMaxBatchSizeForEachGivenBatch() { int ringBufferEntries = 30; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, emptyList(), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 9), event(10, 19), event(20, lastSequenceNumber))); } @Test public void shouldOnlyRewindBatch() { int ringBufferEntries = 30; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(15, 3)), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 14), event(0, 14), event(0, 14), event(0, lastSequenceNumber))); } @Test void shouldInvokeRewindPauseStrategyOnRewind() { int ringBufferEntries = 30; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(15, 3)), lastSequenceNumber, -1); CountingBatchRewindStrategy rewindPauseStrategy = new CountingBatchRewindStrategy(); final BatchEventProcessor eventProcessor = create(eventHandler, rewindPauseStrategy); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 14), event(0, 14), event(0, 14), event(0, lastSequenceNumber))); assertEquals(3, rewindPauseStrategy.count); } @Test void shouldNotInvokeRewindPauseStrategyWhenNoRewindsOccur() { int ringBufferEntries = 30; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(-1, -1)), lastSequenceNumber, -1); CountingBatchRewindStrategy rewindPauseStrategy = new CountingBatchRewindStrategy(); final BatchEventProcessor eventProcessor = create(eventHandler, rewindPauseStrategy); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, lastSequenceNumber))); assertEquals(0, rewindPauseStrategy.count); } @Test void shouldCopeWithTheNanosecondRewindPauseStrategy() { int ringBufferEntries = 30; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, singletonList(rewind(15, 3)), lastSequenceNumber, -1); final BatchEventProcessor eventProcessor = create(eventHandler, new NanosecondPauseBatchRewindStrategy(1000)); eventHandler.setRewindable(eventProcessor); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 14), event(0, 14), event(0, 14), event(0, lastSequenceNumber))); } @Test void shouldGiveUpWhenUsingTheGiveUpRewindStrategy() { int ringBufferEntries = 30; int lastSequenceNumber = ringBufferEntries - 1; fill(ringBuffer, ringBufferEntries); final TestEventHandler eventHandler = new TestEventHandler(values, asList(rewind(15, 99), rewind(25, 99)), lastSequenceNumber, -1); EventuallyGiveUpBatchRewindStrategy batchRewindStrategy = new EventuallyGiveUpBatchRewindStrategy(3); final BatchEventProcessor eventProcessor = create(eventHandler, batchRewindStrategy); eventHandler.setRewindable(eventProcessor); AtomicReference exceptionHandled = new AtomicReference<>(); eventProcessor.setExceptionHandler(new StubExceptionHandler(exceptionHandled)); eventProcessor.run(); assertThat(values, containsExactSequence( event(0, 14), event(0, 14), event(0, 14), event(16, 24), // unable to process 15 so it ends up skipping it event(16, 24), event(16, 24), event(26, lastSequenceNumber))); // unable to process 25 so it ends up skipping it } @Test void shouldNotAllowNullBatchRewindStrategy() { final TestEventHandler eventHandler = new TestEventHandler(values, asList(rewind(15, 99), rewind(25, 99)), -1, -1); final BatchEventProcessorBuilder batchEventProcessorBuilder = new BatchEventProcessorBuilder(); assertThrows(NullPointerException.class, () -> batchEventProcessorBuilder.build(ringBuffer, ringBuffer.newBarrier(), eventHandler, null)); } private static ForceRewindSequence rewind(final long sequenceNumberToFailOn, final long timesToFail) { return new ForceRewindSequence(sequenceNumberToFailOn, timesToFail); } private EventRangeExpectation event(final long sequenceStart, final long sequenceEnd) { return new EventRangeExpectation(sequenceStart, sequenceEnd, false); } private BatchEventProcessor create(final TestEventHandler eventHandler) { return create(eventHandler, new SimpleBatchRewindStrategy()); } private BatchEventProcessor create(final TestEventHandler eventHandler, final BatchRewindStrategy batchRewindStrategy) { return new BatchEventProcessorBuilder().build( ringBuffer, ringBuffer.newBarrier(), eventHandler, batchRewindStrategy); } private static final class TestEventHandler implements RewindableEventHandler { private final List values; private BatchEventProcessor processor; private final List forceRewindSequences; private final long exitValue; private final int nonRewindableErrorSequence; private TestEventHandler( final List values, final List forceRewindSequences, final long exitValue, final int nonRewindableErrorSequence) { this.values = values; this.forceRewindSequences = forceRewindSequences; this.exitValue = exitValue; this.nonRewindableErrorSequence = nonRewindableErrorSequence; } public void setRewindable(final BatchEventProcessor processor) { this.processor = processor; } @Override public void onEvent(final LongEvent event, final long sequence, final boolean endOfBatch) throws RewindableException { if (sequence == nonRewindableErrorSequence) { throw new RuntimeException("not rewindable"); } Optional maybeForceRewindSequence = this.forceRewindSequences.stream() .filter(r -> r.sequenceNumberToFailOn == sequence).findFirst(); if (maybeForceRewindSequence.isPresent()) { ForceRewindSequence forceRewindSequence = maybeForceRewindSequence.get(); if (forceRewindSequence.numberOfTimesRewound != forceRewindSequence.timesToFail) { forceRewindSequence.numberOfTimesRewound++; throw new RewindableException(new RuntimeException()); } } values.add(new EventResult(event.get(), false)); if (sequence == exitValue) { processor.halt(); } } } private static void fill(final RingBuffer ringBuffer, final int batchSize) { for (long l = 0; l < batchSize; l++) { final long next = ringBuffer.next(); ringBuffer.get(next).set(l); ringBuffer.publish(next); } } private static Matcher> containsExactSequence(final EventRangeExpectation... ranges) { return new TypeSafeMatcher<>() { @Override public void describeTo(final Description description) { description.appendValue(Arrays.toString(ranges)); } @Override public boolean matchesSafely(final List item) { int index = 0; for (final EventRangeExpectation range : ranges) { for (long v = range.sequenceStart, end = range.sequenceEnd; v <= end; v++) { final EventResult eventResult = item.get(index++); if (eventResult.sequence != v && eventResult.batchFinish != range.batchFinish) { return false; } } } return item.size() == index; } }; } private static final class EventRangeExpectation { private final long sequenceStart; private final long sequenceEnd; private final boolean batchFinish; EventRangeExpectation(final long sequenceStart, final long sequenceEnd, final boolean batchFinish) { this.sequenceStart = sequenceStart; this.sequenceEnd = sequenceEnd; this.batchFinish = batchFinish; } @Override public String toString() { return "{" + sequenceStart + "," + sequenceEnd + "," + batchFinish + '}'; } } private static final class EventResult { final long sequence; final boolean batchFinish; private EventResult(final long sequence, final boolean batchFinish) { this.sequence = sequence; this.batchFinish = batchFinish; } @Override public String toString() { return "{" + sequence + "," + batchFinish + '}'; } } private static final class CountingBatchRewindStrategy implements BatchRewindStrategy { int count = 0; @Override public RewindAction handleRewindException(final RewindableException e, final int retriesAttempted) { count++; return RewindAction.REWIND; } } private static final class ForceRewindSequence { final long sequenceNumberToFailOn; final long timesToFail; long numberOfTimesRewound = 0; private ForceRewindSequence(final long sequenceNumberToFailOn, final long timesToFail) { this.sequenceNumberToFailOn = sequenceNumberToFailOn; this.timesToFail = timesToFail; } } } ================================================ FILE: src/test/java/com/lmax/disruptor/RingBufferEventMatcher.java ================================================ package com.lmax.disruptor; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import static org.hamcrest.CoreMatchers.is; final class RingBufferEventMatcher extends TypeSafeMatcher> { private final Matcher[] expectedValueMatchers; private RingBufferEventMatcher(final Matcher[] expectedValueMatchers) { this.expectedValueMatchers = expectedValueMatchers; } public static RingBufferEventMatcher ringBufferWithEvents(final Matcher... valueMatchers) { return new RingBufferEventMatcher(valueMatchers); } public static RingBufferEventMatcher ringBufferWithEvents(final Object... values) { Matcher[] valueMatchers = new Matcher[values.length]; for (int i = 0; i < values.length; i++) { final Object value = values[i]; valueMatchers[i] = is(value); } return new RingBufferEventMatcher(valueMatchers); } @Override public boolean matchesSafely(final RingBuffer ringBuffer) { boolean matches = true; for (int i = 0; i < expectedValueMatchers.length; i++) { final Matcher expectedValueMatcher = expectedValueMatchers[i]; matches &= expectedValueMatcher.matches(ringBuffer.get(i)[0]); } return matches; } @Override public void describeTo(final Description description) { description.appendText("Expected ring buffer with events matching: "); for (Matcher expectedValueMatcher : expectedValueMatchers) { expectedValueMatcher.describeTo(description); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/RingBufferTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.StubEvent; import com.lmax.disruptor.support.TestWaiter; import com.lmax.disruptor.util.DaemonThreadFactory; import org.junit.jupiter.api.Test; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import static com.lmax.disruptor.RingBuffer.createMultiProducer; import static com.lmax.disruptor.RingBufferEventMatcher.ringBufferWithEvents; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class RingBufferTest { private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); private final RingBuffer ringBuffer = RingBuffer.createMultiProducer(StubEvent.EVENT_FACTORY, 32); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); { ringBuffer.addGatingSequences(new NoOpEventProcessor(ringBuffer).getSequence()); } @Test public void shouldClaimAndGet() throws Exception { assertEquals(SingleProducerSequencer.INITIAL_CURSOR_VALUE, ringBuffer.getCursor()); StubEvent expectedEvent = new StubEvent(2701); ringBuffer.publishEvent(StubEvent.TRANSLATOR, expectedEvent.getValue(), expectedEvent.getTestString()); long sequence = sequenceBarrier.waitFor(0); assertEquals(0, sequence); StubEvent event = ringBuffer.get(sequence); assertEquals(expectedEvent, event); assertEquals(0L, ringBuffer.getCursor()); } @Test public void shouldClaimAndGetInSeparateThread() throws Exception { Future> messages = getMessages(0, 0); StubEvent expectedEvent = new StubEvent(2701); ringBuffer.publishEvent(StubEvent.TRANSLATOR, expectedEvent.getValue(), expectedEvent.getTestString()); assertEquals(expectedEvent, messages.get().get(0)); } @Test public void shouldClaimAndGetMultipleMessages() throws Exception { int numMessages = ringBuffer.getBufferSize(); for (int i = 0; i < numMessages; i++) { ringBuffer.publishEvent(StubEvent.TRANSLATOR, i, ""); } long expectedSequence = numMessages - 1; long available = sequenceBarrier.waitFor(expectedSequence); assertEquals(expectedSequence, available); for (int i = 0; i < numMessages; i++) { assertEquals(i, ringBuffer.get(i).getValue()); } } @Test public void shouldWrap() throws Exception { int numMessages = ringBuffer.getBufferSize(); int offset = 1000; for (int i = 0; i < numMessages + offset; i++) { ringBuffer.publishEvent(StubEvent.TRANSLATOR, i, ""); } long expectedSequence = numMessages + offset - 1; long available = sequenceBarrier.waitFor(expectedSequence); assertEquals(expectedSequence, available); for (int i = offset; i < numMessages + offset; i++) { assertEquals(i, ringBuffer.get(i).getValue()); } } @Test public void shouldPreventWrapping() throws Exception { Sequence sequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); final RingBuffer ringBuffer = createMultiProducer(StubEvent.EVENT_FACTORY, 4); ringBuffer.addGatingSequences(sequence); ringBuffer.publishEvent(StubEvent.TRANSLATOR, 0, "0"); ringBuffer.publishEvent(StubEvent.TRANSLATOR, 1, "1"); ringBuffer.publishEvent(StubEvent.TRANSLATOR, 2, "2"); ringBuffer.publishEvent(StubEvent.TRANSLATOR, 3, "3"); assertFalse(ringBuffer.tryPublishEvent(StubEvent.TRANSLATOR, 3, "3")); } @Test public void shouldThrowExceptionIfBufferIsFull() throws Exception { ringBuffer.addGatingSequences(new Sequence(ringBuffer.getBufferSize())); for (int i = 0; i < ringBuffer.getBufferSize(); i++) { ringBuffer.publish(ringBuffer.tryNext()); } assertThrows(InsufficientCapacityException.class, ringBuffer::tryNext); } @Test public void shouldPreventPublishersOvertakingEventProcessorWrapPoint() throws InterruptedException { final int ringBufferSize = 16; final CountDownLatch latch = new CountDownLatch(ringBufferSize); final AtomicBoolean publisherComplete = new AtomicBoolean(false); final RingBuffer buffer2 = createMultiProducer(StubEvent.EVENT_FACTORY, ringBufferSize); final TestEventProcessor processor = new TestEventProcessor(buffer2.newBarrier()); buffer2.addGatingSequences(processor.getSequence()); Thread thread = new Thread( () -> { // Attempt to put in enough events to wrap around the ringbuffer for (int i = 0; i < ringBufferSize + 1; i++) { long sequence = buffer2.next(); StubEvent event = buffer2.get(sequence); event.setValue(i); buffer2.publish(sequence); latch.countDown(); } // Only marked complete after enough events published that the ringbuffer must have wrapped publisherComplete.set(true); }); thread.start(); latch.await(); // Publisher should not be complete, blocked at RingBuffer::next assertFalse(publisherComplete.get()); // Run the processor, freeing up entries in the ringbuffer for the producer to continue and "complete" processor.run(); thread.join(); // Check producer completes, ideally this should be in some kind of waiter assertTrue(publisherComplete.get()); } @Test public void shouldPublishEvent() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); final EventTranslator translator = new NoArgEventTranslator(); ringBuffer.publishEvent(translator); ringBuffer.tryPublishEvent(translator); assertThat(ringBuffer, ringBufferWithEvents(0L, 1L)); } @Test public void shouldPublishEventOneArg() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); ringBuffer.publishEvent(translator, "Foo"); ringBuffer.tryPublishEvent(translator, "Foo"); assertThat(ringBuffer, ringBufferWithEvents("Foo-0", "Foo-1")); } @Test public void shouldPublishEventTwoArg() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); ringBuffer.publishEvent(translator, "Foo", "Bar"); ringBuffer.tryPublishEvent(translator, "Foo", "Bar"); assertThat(ringBuffer, ringBufferWithEvents("FooBar-0", "FooBar-1")); } @Test public void shouldPublishEventThreeArg() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); ringBuffer.publishEvent(translator, "Foo", "Bar", "Baz"); ringBuffer.tryPublishEvent(translator, "Foo", "Bar", "Baz"); assertThat(ringBuffer, ringBufferWithEvents("FooBarBaz-0", "FooBarBaz-1")); } @Test public void shouldPublishEventVarArg() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorVararg translator = new VarArgEventTranslator(); ringBuffer.publishEvent(translator, "Foo", "Bar", "Baz", "Bam"); ringBuffer.tryPublishEvent(translator, "Foo", "Bar", "Baz", "Bam"); assertThat(ringBuffer, ringBufferWithEvents("FooBarBazBam-0", "FooBarBazBam-1")); } @SuppressWarnings("unchecked") @Test public void shouldPublishEvents() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); final EventTranslator eventTranslator = new NoArgEventTranslator(); final EventTranslator[] translators = new EventTranslator[]{eventTranslator, eventTranslator}; ringBuffer.publishEvents(translators); assertTrue(ringBuffer.tryPublishEvents(translators)); assertThat(ringBuffer, ringBufferWithEvents(0L, 1L, 2L, 3L)); } @SuppressWarnings("unchecked") @Test public void shouldNotPublishEventsIfBatchIsLargerThanRingBuffer() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); final EventTranslator eventTranslator = new NoArgEventTranslator(); final EventTranslator[] translators = new EventTranslator[]{eventTranslator, eventTranslator, eventTranslator, eventTranslator, eventTranslator}; try { ringBuffer.tryPublishEvents(translators); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @SuppressWarnings("unchecked") @Test public void shouldPublishEventsWithBatchSizeOfOne() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); final EventTranslator eventTranslator = new NoArgEventTranslator(); final EventTranslator[] translators = new EventTranslator[]{eventTranslator, eventTranslator, eventTranslator}; ringBuffer.publishEvents(translators, 0, 1); assertTrue(ringBuffer.tryPublishEvents(translators, 0, 1)); assertThat( ringBuffer, ringBufferWithEvents( is((Object) 0L), is((Object) 1L), is(nullValue()), is( nullValue()))); } @SuppressWarnings("unchecked") @Test public void shouldPublishEventsWithinBatch() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); final EventTranslator eventTranslator = new NoArgEventTranslator(); final EventTranslator[] translators = new EventTranslator[]{eventTranslator, eventTranslator, eventTranslator}; ringBuffer.publishEvents(translators, 1, 2); assertTrue(ringBuffer.tryPublishEvents(translators, 1, 2)); assertThat(ringBuffer, ringBufferWithEvents(0L, 1L, 2L, 3L)); } @Test public void shouldPublishEventsOneArg() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); ringBuffer.publishEvents(translator, new String[]{"Foo", "Foo"}); assertTrue(ringBuffer.tryPublishEvents(translator, new String[]{"Foo", "Foo"})); assertThat(ringBuffer, ringBufferWithEvents("Foo-0", "Foo-1", "Foo-2", "Foo-3")); } @Test public void shouldNotPublishEventsOneArgIfBatchIsLargerThanRingBuffer() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { ringBuffer.tryPublishEvents(translator, new String[]{"Foo", "Foo", "Foo", "Foo", "Foo"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldPublishEventsOneArgBatchSizeOfOne() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); ringBuffer.publishEvents(translator, 0, 1, new String[]{"Foo", "Foo"}); assertTrue(ringBuffer.tryPublishEvents(translator, 0, 1, new String[]{"Foo", "Foo"})); assertThat( ringBuffer, ringBufferWithEvents( is((Object) "Foo-0"), is((Object) "Foo-1"), is(nullValue()), is( nullValue()))); } @Test public void shouldPublishEventsOneArgWithinBatch() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); ringBuffer.publishEvents(translator, 1, 2, new String[]{"Foo", "Foo", "Foo"}); assertTrue(ringBuffer.tryPublishEvents(translator, 1, 2, new String[]{"Foo", "Foo", "Foo"})); assertThat(ringBuffer, ringBufferWithEvents("Foo-0", "Foo-1", "Foo-2", "Foo-3")); } @Test public void shouldPublishEventsTwoArg() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); ringBuffer.publishEvents(translator, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); ringBuffer.tryPublishEvents(translator, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); assertThat(ringBuffer, ringBufferWithEvents("FooBar-0", "FooBar-1", "FooBar-2", "FooBar-3")); } @Test public void shouldNotPublishEventsITwoArgIfBatchSizeIsBiggerThanRingBuffer() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, new String[]{"Foo", "Foo", "Foo", "Foo", "Foo"}, new String[]{"Bar", "Bar", "Bar", "Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldPublishEventsTwoArgWithBatchSizeOfOne() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); ringBuffer.publishEvents(translator, 0, 1, new String[]{"Foo0", "Foo1"}, new String[]{"Bar0", "Bar1"}); ringBuffer.tryPublishEvents(translator, 0, 1, new String[]{"Foo2", "Foo3"}, new String[]{"Bar2", "Bar3"}); assertThat( ringBuffer, ringBufferWithEvents( is((Object) "Foo0Bar0-0"), is((Object) "Foo2Bar2-1"), is( nullValue()), is(nullValue()))); } @Test public void shouldPublishEventsTwoArgWithinBatch() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); ringBuffer.publishEvents( translator, 1, 2, new String[]{"Foo0", "Foo1", "Foo2"}, new String[]{"Bar0", "Bar1", "Bar2"}); ringBuffer.tryPublishEvents( translator, 1, 2, new String[]{"Foo3", "Foo4", "Foo5"}, new String[]{"Bar3", "Bar4", "Bar5"}); assertThat(ringBuffer, ringBufferWithEvents("Foo1Bar1-0", "Foo2Bar2-1", "Foo4Bar4-2", "Foo5Bar5-3")); } @Test public void shouldPublishEventsThreeArg() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); ringBuffer.publishEvents( translator, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); ringBuffer.tryPublishEvents( translator, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); assertThat(ringBuffer, ringBufferWithEvents("FooBarBaz-0", "FooBarBaz-1", "FooBarBaz-2", "FooBarBaz-3")); } @Test public void shouldNotPublishEventsThreeArgIfBatchIsLargerThanRingBuffer() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, new String[]{"Foo", "Foo", "Foo", "Foo", "Foo"}, new String[]{"Bar", "Bar", "Bar", "Bar", "Bar"}, new String[]{"Baz", "Baz", "Baz", "Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldPublishEventsThreeArgBatchSizeOfOne() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); ringBuffer.publishEvents( translator, 0, 1, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); ringBuffer.tryPublishEvents( translator, 0, 1, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); assertThat( ringBuffer, ringBufferWithEvents( is((Object) "FooBarBaz-0"), is((Object) "FooBarBaz-1"), is( nullValue()), is(nullValue()))); } @Test public void shouldPublishEventsThreeArgWithinBatch() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); ringBuffer.publishEvents( translator, 1, 2, new String[]{"Foo0", "Foo1", "Foo2"}, new String[]{"Bar0", "Bar1", "Bar2"}, new String[]{"Baz0", "Baz1", "Baz2"} ); assertTrue( ringBuffer.tryPublishEvents( translator, 1, 2, new String[]{"Foo3", "Foo4", "Foo5"}, new String[]{"Bar3", "Bar4", "Bar5"}, new String[]{"Baz3", "Baz4", "Baz5"})); assertThat( ringBuffer, ringBufferWithEvents( "Foo1Bar1Baz1-0", "Foo2Bar2Baz2-1", "Foo4Bar4Baz4-2", "Foo5Bar5Baz5-3")); } @Test public void shouldPublishEventsVarArg() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorVararg translator = new VarArgEventTranslator(); ringBuffer.publishEvents( translator, new String[]{"Foo", "Bar", "Baz", "Bam"}, new String[]{"Foo", "Bar", "Baz", "Bam"}); assertTrue( ringBuffer.tryPublishEvents( translator, new String[]{"Foo", "Bar", "Baz", "Bam"}, new String[]{"Foo", "Bar", "Baz", "Bam"})); assertThat( ringBuffer, ringBufferWithEvents( "FooBarBazBam-0", "FooBarBazBam-1", "FooBarBazBam-2", "FooBarBazBam-3")); } @Test public void shouldNotPublishEventsVarArgIfBatchIsLargerThanRingBuffer() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorVararg translator = new VarArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, new String[]{"Foo", "Bar", "Baz", "Bam"}, new String[]{"Foo", "Bar", "Baz", "Bam"}, new String[]{"Foo", "Bar", "Baz", "Bam"}, new String[]{"Foo", "Bar", "Baz", "Bam"}, new String[]{"Foo", "Bar", "Baz", "Bam"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldPublishEventsVarArgBatchSizeOfOne() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorVararg translator = new VarArgEventTranslator(); ringBuffer.publishEvents( translator, 0, 1, new String[]{"Foo", "Bar", "Baz", "Bam"}, new String[]{"Foo", "Bar", "Baz", "Bam"}); assertTrue( ringBuffer.tryPublishEvents( translator, 0, 1, new String[]{"Foo", "Bar", "Baz", "Bam"}, new String[]{"Foo", "Bar", "Baz", "Bam"})); assertThat( ringBuffer, ringBufferWithEvents( is((Object) "FooBarBazBam-0"), is((Object) "FooBarBazBam-1"), is( nullValue()), is(nullValue()))); } @Test public void shouldPublishEventsVarArgWithinBatch() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorVararg translator = new VarArgEventTranslator(); ringBuffer.publishEvents( translator, 1, 2, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{"Foo2", "Bar2", "Baz2", "Bam2"}); assertTrue( ringBuffer.tryPublishEvents( translator, 1, 2, new String[]{"Foo3", "Bar3", "Baz3", "Bam3"}, new String[]{"Foo4", "Bar4", "Baz4", "Bam4"}, new String[]{"Foo5", "Bar5", "Baz5", "Bam5"})); assertThat( ringBuffer, ringBufferWithEvents( "Foo1Bar1Baz1Bam1-0", "Foo2Bar2Baz2Bam2-1", "Foo4Bar4Baz4Bam4-2", "Foo5Bar5Baz5Bam5-3")); } @SuppressWarnings("unchecked") @Test public void shouldNotPublishEventsWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslator translator = new NoArgEventTranslator(); try { ringBuffer.publishEvents(new EventTranslator[]{translator, translator, translator, translator}, 1, 0); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @SuppressWarnings("unchecked") @Test public void shouldNotTryPublishEventsWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslator translator = new NoArgEventTranslator(); try { ringBuffer.tryPublishEvents(new EventTranslator[]{translator, translator, translator, translator}, 1, 0); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @SuppressWarnings("unchecked") @Test public void shouldNotPublishEventsWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslator translator = new NoArgEventTranslator(); try { ringBuffer.publishEvents(new EventTranslator[]{translator, translator, translator}, 1, 3); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @SuppressWarnings("unchecked") @Test public void shouldNotTryPublishEventsWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslator translator = new NoArgEventTranslator(); try { ringBuffer.tryPublishEvents(new EventTranslator[]{translator, translator, translator}, 1, 3); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @SuppressWarnings("unchecked") @Test public void shouldNotPublishEventsWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslator translator = new NoArgEventTranslator(); try { ringBuffer.publishEvents(new EventTranslator[]{translator, translator, translator, translator}, 1, -1); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @SuppressWarnings("unchecked") @Test public void shouldNotTryPublishEventsWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslator translator = new NoArgEventTranslator(); try { ringBuffer.tryPublishEvents(new EventTranslator[]{translator, translator, translator, translator}, 1, -1); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @SuppressWarnings("unchecked") @Test public void shouldNotPublishEventsWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslator translator = new NoArgEventTranslator(); try { ringBuffer.publishEvents(new EventTranslator[]{translator, translator, translator, translator}, -1, 2); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @SuppressWarnings("unchecked") @Test public void shouldNotTryPublishEventsWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslator translator = new NoArgEventTranslator(); try { ringBuffer.tryPublishEvents(new EventTranslator[]{translator, translator, translator, translator}, -1, 2); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsOneArgWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { ringBuffer.publishEvents(translator, 1, 0, new String[]{"Foo", "Foo"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsOneArgWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { ringBuffer.tryPublishEvents(translator, 1, 0, new String[]{"Foo", "Foo"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsOneArgWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { ringBuffer.publishEvents(translator, 1, 3, new String[]{"Foo", "Foo"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsOneArgWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { ringBuffer.publishEvents(translator, 1, -1, new String[]{"Foo", "Foo"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsOneArgWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { ringBuffer.publishEvents(translator, -1, 2, new String[]{"Foo", "Foo"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsOneArgWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { ringBuffer.tryPublishEvents(translator, 1, 3, new String[]{"Foo", "Foo"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsOneArgWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { assertFalse(ringBuffer.tryPublishEvents(translator, 1, -1, new String[]{"Foo", "Foo"})); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsOneArgWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorOneArg translator = new OneArgEventTranslator(); try { ringBuffer.tryPublishEvents(translator, -1, 2, new String[]{"Foo", "Foo"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsTwoArgWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.publishEvents(translator, 1, 0, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsTwoArgWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.tryPublishEvents(translator, 1, 0, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsTwoArgWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.publishEvents(translator, 1, 3, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsTwoArgWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.publishEvents(translator, 1, -1, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsTwoArgWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.publishEvents(translator, -1, 2, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsTwoArgWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.tryPublishEvents(translator, 1, 3, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsTwoArgWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.tryPublishEvents(translator, 1, -1, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsTwoArgWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorTwoArg translator = new TwoArgEventTranslator(); try { ringBuffer.tryPublishEvents(translator, -1, 2, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsThreeArgWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.publishEvents( translator, 1, 0, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsThreeArgWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, 1, 0, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsThreeArgWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.publishEvents( translator, 1, 3, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsThreeArgWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.publishEvents( translator, 1, -1, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsThreeArgWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.publishEvents( translator, -1, 2, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsThreeArgWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, 1, 3, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsThreeArgWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, 1, -1, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsThreeArgWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); EventTranslatorThreeArg translator = new ThreeArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, -1, 2, new String[]{"Foo", "Foo"}, new String[]{"Bar", "Bar"}, new String[]{"Baz", "Baz"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsVarArgWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); VarArgEventTranslator translator = new VarArgEventTranslator(); try { ringBuffer.publishEvents( translator, 1, 0, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{ "Foo2", "Bar2", "Baz2", "Bam2" }); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsVarArgWhenBatchSizeIs0() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); VarArgEventTranslator translator = new VarArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, 1, 0, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{"Foo2", "Bar2", "Baz2", "Bam2"}); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsVarArgWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); VarArgEventTranslator translator = new VarArgEventTranslator(); try { ringBuffer.publishEvents( translator, 1, 3, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{ "Foo2", "Bar2", "Baz2", "Bam2" }); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsVarArgWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); VarArgEventTranslator translator = new VarArgEventTranslator(); try { ringBuffer.publishEvents( translator, 1, -1, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{ "Foo2", "Bar2", "Baz2", "Bam2" }); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotPublishEventsVarArgWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); VarArgEventTranslator translator = new VarArgEventTranslator(); try { ringBuffer.publishEvents( translator, -1, 2, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{ "Foo2", "Bar2", "Baz2", "Bam2" }); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsVarArgWhenBatchExtendsPastEndOfArray() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); VarArgEventTranslator translator = new VarArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, 1, 3, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{ "Foo2", "Bar2", "Baz2", "Bam2" }); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsVarArgWhenBatchSizeIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); VarArgEventTranslator translator = new VarArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, 1, -1, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{ "Foo2", "Bar2", "Baz2", "Bam2" }); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldNotTryPublishEventsVarArgWhenBatchStartsAtIsNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 4); VarArgEventTranslator translator = new VarArgEventTranslator(); try { ringBuffer.tryPublishEvents( translator, -1, 2, new String[]{"Foo0", "Bar0", "Baz0", "Bam0"}, new String[]{"Foo1", "Bar1", "Baz1", "Bam1"}, new String[]{ "Foo2", "Bar2", "Baz2", "Bam2" }); } finally { assertEmptyRingBuffer(ringBuffer); } }); } @Test public void shouldAddAndRemoveSequences() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(new ArrayFactory(1), 16); Sequence sequenceThree = new Sequence(-1); Sequence sequenceSeven = new Sequence(-1); ringBuffer.addGatingSequences(sequenceThree, sequenceSeven); for (int i = 0; i < 10; i++) { ringBuffer.publish(ringBuffer.next()); } sequenceThree.set(3); sequenceSeven.set(7); assertThat(ringBuffer.getMinimumGatingSequence(), is(3L)); assertTrue(ringBuffer.removeGatingSequence(sequenceThree)); assertThat(ringBuffer.getMinimumGatingSequence(), is(7L)); } private Future> getMessages(final long initial, final long toWaitFor) throws InterruptedException, BrokenBarrierException { final CyclicBarrier cyclicBarrier = new CyclicBarrier(2); final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); final Future> f = executor.submit( new TestWaiter( cyclicBarrier, sequenceBarrier, ringBuffer, initial, toWaitFor)); cyclicBarrier.await(); return f; } private void assertEmptyRingBuffer(final RingBuffer ringBuffer) { assertThat(ringBuffer.get(0)[0], is(nullValue())); assertThat(ringBuffer.get(1)[0], is(nullValue())); assertThat(ringBuffer.get(2)[0], is(nullValue())); assertThat(ringBuffer.get(3)[0], is(nullValue())); } private static final class TestEventProcessor implements EventProcessor { private final SequenceBarrier sequenceBarrier; private final Sequence sequence = new Sequence(SingleProducerSequencer.INITIAL_CURSOR_VALUE); private final AtomicBoolean running = new AtomicBoolean(); TestEventProcessor(final SequenceBarrier sequenceBarrier) { this.sequenceBarrier = sequenceBarrier; } @Override public Sequence getSequence() { return sequence; } @Override public void halt() { running.set(false); } @Override public boolean isRunning() { return running.get(); } @Override public void run() { if (!running.compareAndSet(false, true)) { throw new IllegalStateException("Already running"); } try { sequenceBarrier.waitFor(0L); } catch (Exception ex) { throw new RuntimeException(ex); } sequence.set(sequence.get() + 1L); } } private static class ArrayFactory implements EventFactory { private final int size; ArrayFactory(final int size) { this.size = size; } @Override public Object[] newInstance() { return new Object[size]; } } private static class NoArgEventTranslator implements EventTranslator { @Override public void translateTo(final Object[] event, final long sequence) { event[0] = sequence; } } private static class VarArgEventTranslator implements EventTranslatorVararg { @Override public void translateTo(final Object[] event, final long sequence, final Object... args) { event[0] = (String) args[0] + args[1] + args[2] + args[3] + "-" + sequence; } } private static class ThreeArgEventTranslator implements EventTranslatorThreeArg { @Override public void translateTo(final Object[] event, final long sequence, final String arg0, final String arg1, final String arg2) { event[0] = arg0 + arg1 + arg2 + "-" + sequence; } } private static class TwoArgEventTranslator implements EventTranslatorTwoArg { @Override public void translateTo(final Object[] event, final long sequence, final String arg0, final String arg1) { event[0] = arg0 + arg1 + "-" + sequence; } } private static class OneArgEventTranslator implements EventTranslatorOneArg { @Override public void translateTo(final Object[] event, final long sequence, final String arg0) { event[0] = arg0 + "-" + sequence; } } } ================================================ FILE: src/test/java/com/lmax/disruptor/RingBufferWithAssertingStubTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.support.StubEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.concurrent.ThreadLocalRandom; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; public class RingBufferWithAssertingStubTest { private RingBuffer ringBuffer; private Sequencer sequencer; @BeforeEach public void setUp() { sequencer = new AssertingSequencer(16); ringBuffer = new RingBuffer<>(StubEvent.EVENT_FACTORY, sequencer); } @Test public void shouldDelegateNextAndPublish() { ringBuffer.publish(ringBuffer.next()); } @Test public void shouldDelegateTryNextAndPublish() throws Exception { ringBuffer.publish(ringBuffer.tryNext()); } @Test public void shouldDelegateNextNAndPublish() throws Exception { long hi = ringBuffer.next(10); ringBuffer.publish(hi - 9, hi); } @Test public void shouldDelegateTryNextNAndPublish() throws Exception { long hi = ringBuffer.tryNext(10); ringBuffer.publish(hi - 9, hi); } private static final class AssertingSequencer implements Sequencer { private final int size; private long lastBatchSize = -1; private long lastValue = -1; private AssertingSequencer(final int size) { this.size = size; } @Override public int getBufferSize() { return size; } @Override public boolean hasAvailableCapacity(final int requiredCapacity) { return requiredCapacity <= size; } @Override public long remainingCapacity() { return size; } @Override public long next() { lastValue = ThreadLocalRandom.current().nextLong(0, 1000000); lastBatchSize = 1; return lastValue; } @Override public long next(final int n) { lastValue = ThreadLocalRandom.current().nextLong(n, 1000000); lastBatchSize = n; return lastValue; } @Override public long tryNext() throws InsufficientCapacityException { return next(); } @Override public long tryNext(final int n) throws InsufficientCapacityException { return next(n); } @Override public void publish(final long sequence) { assertThat(sequence, is(lastValue)); assertThat(lastBatchSize, is(1L)); } @Override public void publish(final long lo, final long hi) { assertThat(hi, is(lastValue)); assertThat((hi - lo) + 1, is(lastBatchSize)); } @Override public long getCursor() { return lastValue; } @Override public void claim(final long sequence) { } @Override public boolean isAvailable(final long sequence) { return false; } @Override public void addGatingSequences(final Sequence... gatingSequences) { } @Override public boolean removeGatingSequence(final Sequence sequence) { return false; } @Override public SequenceBarrier newBarrier(final Sequence... sequencesToTrack) { return null; } @Override public long getMinimumSequence() { return 0; } @Override public long getHighestPublishedSequence(final long nextSequence, final long availableSequence) { return 0; } @Override public EventPoller newPoller(final DataProvider provider, final Sequence... gatingSequences) { return null; } } } ================================================ FILE: src/test/java/com/lmax/disruptor/SequenceBarrierTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.DummyEventProcessor; import com.lmax.disruptor.support.StubEvent; import com.lmax.disruptor.util.Util; import org.junit.jupiter.api.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static com.lmax.disruptor.RingBuffer.createMultiProducer; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public final class SequenceBarrierTest { private final RingBuffer ringBuffer = createMultiProducer(StubEvent.EVENT_FACTORY, 64); public SequenceBarrierTest() { ringBuffer.addGatingSequences(new NoOpEventProcessor(ringBuffer).getSequence()); } @Test public void shouldWaitForWorkCompleteWhereCompleteWorkThresholdIsAhead() throws Exception { final long expectedNumberMessages = 10; final long expectedWorkSequence = 9; fillRingBuffer(expectedNumberMessages); final Sequence sequence1 = new Sequence(expectedNumberMessages); final Sequence sequence2 = new Sequence(expectedWorkSequence); final Sequence sequence3 = new Sequence(expectedNumberMessages); final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(sequence1, sequence2, sequence3); long completedWorkSequence = sequenceBarrier.waitFor(expectedWorkSequence); assertTrue(completedWorkSequence >= expectedWorkSequence); } @Test public void shouldWaitForWorkCompleteWhereAllWorkersAreBlockedOnRingBuffer() throws Exception { long expectedNumberMessages = 10; fillRingBuffer(expectedNumberMessages); final DummyEventProcessor[] workers = new DummyEventProcessor[3]; for (int i = 0, size = workers.length; i < size; i++) { workers[i] = new DummyEventProcessor(); workers[i].setSequence(expectedNumberMessages - 1); } final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(Util.getSequencesFor(workers)); Runnable runnable = () -> { long sequence = ringBuffer.next(); StubEvent event = ringBuffer.get(sequence); event.setValue((int) sequence); ringBuffer.publish(sequence); for (DummyEventProcessor stubWorker : workers) { stubWorker.setSequence(sequence); } }; new Thread(runnable).start(); long expectedWorkSequence = expectedNumberMessages; long completedWorkSequence = sequenceBarrier.waitFor(expectedNumberMessages); assertTrue(completedWorkSequence >= expectedWorkSequence); } @Test public void shouldInterruptDuringBusySpin() throws Exception { final long expectedNumberMessages = 10; fillRingBuffer(expectedNumberMessages); final CountDownLatch latch = new CountDownLatch(3); final Sequence sequence1 = new CountDownLatchSequence(8L, latch); final Sequence sequence2 = new CountDownLatchSequence(8L, latch); final Sequence sequence3 = new CountDownLatchSequence(8L, latch); final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(sequence1, sequence2, sequence3); Thread t = new Thread( () -> assertThrows(AlertException.class, () -> sequenceBarrier.waitFor(expectedNumberMessages - 1))); t.start(); latch.await(3, TimeUnit.SECONDS); sequenceBarrier.alert(); t.join(); } @Test public void shouldWaitForWorkCompleteWhereCompleteWorkThresholdIsBehind() throws Exception { long expectedNumberMessages = 10; fillRingBuffer(expectedNumberMessages); final DummyEventProcessor[] eventProcessors = new DummyEventProcessor[3]; for (int i = 0, size = eventProcessors.length; i < size; i++) { eventProcessors[i] = new DummyEventProcessor(); eventProcessors[i].setSequence(expectedNumberMessages - 2); } final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(Util.getSequencesFor(eventProcessors)); Runnable runnable = () -> { for (DummyEventProcessor stubWorker : eventProcessors) { stubWorker.setSequence(stubWorker.getSequence().get() + 1L); } }; Thread thread = new Thread(runnable); thread.start(); thread.join(); long expectedWorkSequence = expectedNumberMessages - 1; long completedWorkSequence = sequenceBarrier.waitFor(expectedWorkSequence); assertTrue(completedWorkSequence >= expectedWorkSequence); } @Test public void shouldSetAndClearAlertStatus() { SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); assertFalse(sequenceBarrier.isAlerted()); sequenceBarrier.alert(); assertTrue(sequenceBarrier.isAlerted()); sequenceBarrier.clearAlert(); assertFalse(sequenceBarrier.isAlerted()); } private void fillRingBuffer(final long expectedNumberMessages) throws InterruptedException { for (long i = 0; i < expectedNumberMessages; i++) { long sequence = ringBuffer.next(); StubEvent event = ringBuffer.get(sequence); event.setValue((int) i); ringBuffer.publish(sequence); } } private static final class CountDownLatchSequence extends Sequence { private final CountDownLatch latch; private CountDownLatchSequence(final long initialValue, final CountDownLatch latch) { super(initialValue); this.latch = latch; } @Override public long get() { latch.countDown(); return super.get(); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/SequenceGroupTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.TestEvent; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public final class SequenceGroupTest { @Test public void shouldReturnMaxSequenceWhenEmptyGroup() { final SequenceGroup sequenceGroup = new SequenceGroup(); assertEquals(Long.MAX_VALUE, sequenceGroup.get()); } @Test public void shouldAddOneSequenceToGroup() { final Sequence sequence = new Sequence(7L); final SequenceGroup sequenceGroup = new SequenceGroup(); sequenceGroup.add(sequence); assertEquals(sequence.get(), sequenceGroup.get()); } @Test public void shouldNotFailIfTryingToRemoveNotExistingSequence() throws Exception { SequenceGroup group = new SequenceGroup(); group.add(new Sequence()); group.add(new Sequence()); group.remove(new Sequence()); } @Test public void shouldReportTheMinimumSequenceForGroupOfTwo() { final Sequence sequenceThree = new Sequence(3L); final Sequence sequenceSeven = new Sequence(7L); final SequenceGroup sequenceGroup = new SequenceGroup(); sequenceGroup.add(sequenceSeven); sequenceGroup.add(sequenceThree); assertEquals(sequenceThree.get(), sequenceGroup.get()); } @Test public void shouldReportSizeOfGroup() { final SequenceGroup sequenceGroup = new SequenceGroup(); sequenceGroup.add(new Sequence()); sequenceGroup.add(new Sequence()); sequenceGroup.add(new Sequence()); assertEquals(3, sequenceGroup.size()); } @Test public void shouldRemoveSequenceFromGroup() { final Sequence sequenceThree = new Sequence(3L); final Sequence sequenceSeven = new Sequence(7L); final SequenceGroup sequenceGroup = new SequenceGroup(); sequenceGroup.add(sequenceSeven); sequenceGroup.add(sequenceThree); assertEquals(sequenceThree.get(), sequenceGroup.get()); assertTrue(sequenceGroup.remove(sequenceThree)); assertEquals(sequenceSeven.get(), sequenceGroup.get()); assertEquals(1, sequenceGroup.size()); } @Test public void shouldRemoveSequenceFromGroupWhereItBeenAddedMultipleTimes() { final Sequence sequenceThree = new Sequence(3L); final Sequence sequenceSeven = new Sequence(7L); final SequenceGroup sequenceGroup = new SequenceGroup(); sequenceGroup.add(sequenceThree); sequenceGroup.add(sequenceSeven); sequenceGroup.add(sequenceThree); assertEquals(sequenceThree.get(), sequenceGroup.get()); assertTrue(sequenceGroup.remove(sequenceThree)); assertEquals(sequenceSeven.get(), sequenceGroup.get()); assertEquals(1, sequenceGroup.size()); } @Test public void shouldSetGroupSequenceToSameValue() { final Sequence sequenceThree = new Sequence(3L); final Sequence sequenceSeven = new Sequence(7L); final SequenceGroup sequenceGroup = new SequenceGroup(); sequenceGroup.add(sequenceSeven); sequenceGroup.add(sequenceThree); final long expectedSequence = 11L; sequenceGroup.set(expectedSequence); assertEquals(expectedSequence, sequenceThree.get()); assertEquals(expectedSequence, sequenceSeven.get()); } @Test public void shouldAddWhileRunning() throws Exception { RingBuffer ringBuffer = RingBuffer.createSingleProducer(TestEvent.EVENT_FACTORY, 32); final Sequence sequenceThree = new Sequence(3L); final Sequence sequenceSeven = new Sequence(7L); final SequenceGroup sequenceGroup = new SequenceGroup(); sequenceGroup.add(sequenceSeven); for (int i = 0; i < 11; i++) { ringBuffer.publish(ringBuffer.next()); } sequenceGroup.addWhileRunning(ringBuffer, sequenceThree); assertThat(sequenceThree.get(), is(10L)); } } ================================================ FILE: src/test/java/com/lmax/disruptor/SequenceReportingCallbackTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import com.lmax.disruptor.support.StubEvent; import org.junit.jupiter.api.Test; import java.util.concurrent.CountDownLatch; import static com.lmax.disruptor.RingBuffer.createMultiProducer; import static org.junit.jupiter.api.Assertions.assertEquals; public class SequenceReportingCallbackTest { private final CountDownLatch callbackLatch = new CountDownLatch(1); private final CountDownLatch onEndOfBatchLatch = new CountDownLatch(1); @Test public void shouldReportProgressByUpdatingSequenceViaCallback() throws Exception { final RingBuffer ringBuffer = createMultiProducer(StubEvent.EVENT_FACTORY, 16); final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); final EventHandler handler = new TestSequenceReportingEventHandler(); final BatchEventProcessor batchEventProcessor = new BatchEventProcessorBuilder().build( ringBuffer, sequenceBarrier, handler); ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); Thread thread = new Thread(batchEventProcessor); thread.setDaemon(true); thread.start(); assertEquals(-1L, batchEventProcessor.getSequence().get()); ringBuffer.publish(ringBuffer.next()); callbackLatch.await(); assertEquals(0L, batchEventProcessor.getSequence().get()); onEndOfBatchLatch.countDown(); assertEquals(0L, batchEventProcessor.getSequence().get()); batchEventProcessor.halt(); thread.join(); } private class TestSequenceReportingEventHandler implements EventHandler { private Sequence sequenceCallback; @Override public void setSequenceCallback(final Sequence sequenceTrackerCallback) { this.sequenceCallback = sequenceTrackerCallback; } @Override public void onEvent(final StubEvent event, final long sequence, final boolean endOfBatch) throws Exception { sequenceCallback.set(sequence); callbackLatch.countDown(); if (endOfBatch) { onEndOfBatchLatch.await(); } } } } ================================================ FILE: src/test/java/com/lmax/disruptor/SequenceTest.java ================================================ package com.lmax.disruptor; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class SequenceTest { @Test void shouldReturnChangedValueAfterAddAndGet() { final Sequence sequence = new Sequence(0); assertEquals(10, sequence.addAndGet(10)); assertEquals(10, sequence.get()); } @Test void shouldReturnIncrementedValueAfterIncrementAndGet() { final Sequence sequence = new Sequence(0); assertEquals(1, sequence.incrementAndGet()); assertEquals(1, sequence.get()); } @Test void shouldReturnPreviousValueAfterGetAndAdd() { final Sequence sequence = new Sequence(0); assertEquals(0, sequence.getAndAdd(1)); assertEquals(1, sequence.get()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/SequencerTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.support.DummyWaitStrategy; import com.lmax.disruptor.util.DaemonThreadFactory; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Stream; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.provider.Arguments.arguments; public class SequencerTest { private static final int BUFFER_SIZE = 16; private final ExecutorService executor = Executors.newSingleThreadExecutor(DaemonThreadFactory.INSTANCE); private final Sequence gatingSequence = new Sequence(); private static Stream sequencerGenerator() { return Stream.of( arguments(newProducer(ProducerType.SINGLE, new BlockingWaitStrategy())), arguments(newProducer(ProducerType.MULTI, new BlockingWaitStrategy())) ); } private static Stream producerTypeGenerator() { return Stream.of(arguments(ProducerType.SINGLE), arguments(ProducerType.MULTI)); } private static Sequencer newProducer(final ProducerType producerType, final WaitStrategy waitStrategy) { switch (producerType) { case SINGLE: return new SingleProducerSequencer(BUFFER_SIZE, waitStrategy); case MULTI: return new MultiProducerSequencer(BUFFER_SIZE, waitStrategy); default: throw new IllegalStateException(producerType.toString()); } } @Test public void shouldThrowAssertionErrorIfTwoThreadsPublishToSingleProducer() throws InterruptedException { Sequencer sequencer = new SingleProducerSequencer(BUFFER_SIZE, new BlockingWaitStrategy()); Thread otherThread = new Thread(sequencer::next); otherThread.start(); otherThread.join(); assertThrows(AssertionError.class, sequencer::next); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldStartWithInitialValue(final Sequencer sequencer) { assertEquals(0, sequencer.next()); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldBatchClaim(final Sequencer sequencer) { assertEquals(3, sequencer.next(4)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldIndicateHasAvailableCapacity(final Sequencer sequencer) { sequencer.addGatingSequences(gatingSequence); assertTrue(sequencer.hasAvailableCapacity(1)); assertTrue(sequencer.hasAvailableCapacity(BUFFER_SIZE)); assertFalse(sequencer.hasAvailableCapacity(BUFFER_SIZE + 1)); sequencer.publish(sequencer.next()); assertTrue(sequencer.hasAvailableCapacity(BUFFER_SIZE - 1)); assertFalse(sequencer.hasAvailableCapacity(BUFFER_SIZE)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldIndicateNoAvailableCapacity(final Sequencer sequencer) { sequencer.addGatingSequences(gatingSequence); long sequence = sequencer.next(BUFFER_SIZE); sequencer.publish(sequence - (BUFFER_SIZE - 1), sequence); assertFalse(sequencer.hasAvailableCapacity(1)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldHoldUpPublisherWhenBufferIsFull(final Sequencer sequencer) throws InterruptedException { sequencer.addGatingSequences(gatingSequence); final CountDownLatch waitingLatch = new CountDownLatch(1); final CountDownLatch doneLatch = new CountDownLatch(1); final long expectedFullSequence = Sequencer.INITIAL_CURSOR_VALUE + sequencer.getBufferSize(); executor.submit( () -> { long sequence = sequencer.next(BUFFER_SIZE); sequencer.publish(sequence - (BUFFER_SIZE - 1), sequence); assertThat( sequencer.getHighestPublishedSequence(Sequencer.INITIAL_CURSOR_VALUE + 1, sequencer.getCursor()), is(expectedFullSequence)); waitingLatch.countDown(); long next = sequencer.next(); sequencer.publish(next); doneLatch.countDown(); }); waitingLatch.await(); assertThat( sequencer.getHighestPublishedSequence(expectedFullSequence, sequencer.getCursor()), is(expectedFullSequence)); gatingSequence.set(Sequencer.INITIAL_CURSOR_VALUE + 1L); doneLatch.await(); assertThat(sequencer.getHighestPublishedSequence(expectedFullSequence, sequencer.getCursor()), is(expectedFullSequence + 1L)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldThrowInsufficientCapacityExceptionWhenSequencerIsFull(final Sequencer sequencer) throws Exception { assertThrows(InsufficientCapacityException.class, () -> { sequencer.addGatingSequences(gatingSequence); for (int i = 0; i < BUFFER_SIZE; i++) { sequencer.next(); } sequencer.tryNext(); }); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldCalculateRemainingCapacity(final Sequencer sequencer) throws Exception { sequencer.addGatingSequences(gatingSequence); assertThat(sequencer.remainingCapacity(), is((long) BUFFER_SIZE)); for (int i = 1; i < BUFFER_SIZE; i++) { sequencer.next(); assertThat(sequencer.remainingCapacity(), is((long) BUFFER_SIZE - i)); } } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldNotBeAvailableUntilPublished(final Sequencer sequencer) throws Exception { long next = sequencer.next(6); for (int i = 0; i <= 5; i++) { assertThat(sequencer.isAvailable(i), is(false)); } sequencer.publish(next - (6 - 1), next); for (int i = 0; i <= 5; i++) { assertThat(sequencer.isAvailable(i), is(true)); } assertThat(sequencer.isAvailable(6), is(false)); } @ParameterizedTest @MethodSource("producerTypeGenerator") public void shouldNotifyWaitStrategyOnPublish(final ProducerType producerType) throws Exception { final DummyWaitStrategy waitStrategy = new DummyWaitStrategy(); final Sequenced sequencer = newProducer(producerType, waitStrategy); sequencer.publish(sequencer.next()); assertThat(waitStrategy.signalAllWhenBlockingCalls, is(1)); } @ParameterizedTest @MethodSource("producerTypeGenerator") public void shouldNotifyWaitStrategyOnPublishBatch(final ProducerType producerType) throws Exception { final DummyWaitStrategy waitStrategy = new DummyWaitStrategy(); final Sequenced sequencer = newProducer(producerType, waitStrategy); long next = sequencer.next(4); sequencer.publish(next - (4 - 1), next); assertThat(waitStrategy.signalAllWhenBlockingCalls, is(1)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldWaitOnPublication(final Sequencer sequencer) throws Exception { SequenceBarrier barrier = sequencer.newBarrier(); long next = sequencer.next(10); long lo = next - (10 - 1); long mid = next - 5; for (long l = lo; l < mid; l++) { sequencer.publish(l); } assertThat(barrier.waitFor(-1), is(mid - 1)); for (long l = mid; l <= next; l++) { sequencer.publish(l); } assertThat(barrier.waitFor(-1), is(next)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldTryNext(final Sequencer sequencer) throws Exception { sequencer.addGatingSequences(gatingSequence); for (int i = 0; i < BUFFER_SIZE; i++) { sequencer.publish(sequencer.tryNext()); } assertThrows(InsufficientCapacityException.class, sequencer::tryNext); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldClaimSpecificSequence(final Sequencer sequencer) throws Exception { long sequence = 14L; sequencer.claim(sequence); sequencer.publish(sequence); assertThat(sequencer.next(), is(sequence + 1)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldNotAllowBulkNextLessThanZero(final Sequencer sequencer) throws Exception { assertThrows(IllegalArgumentException.class, () -> sequencer.next(-1)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldNotAllowBulkNextOfZero(final Sequencer sequencer) throws Exception { assertThrows(IllegalArgumentException.class, () -> sequencer.next(0)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldNotAllowBulkTryNextLessThanZero(final Sequencer sequencer) throws Exception { assertThrows(IllegalArgumentException.class, () -> sequencer.tryNext(-1)); } @ParameterizedTest @MethodSource("sequencerGenerator") public void shouldNotAllowBulkTryNextOfZero(final Sequencer sequencer) throws Exception { assertThrows(IllegalArgumentException.class, () -> sequencer.tryNext(0)); } @ParameterizedTest @MethodSource("sequencerGenerator") void sequencesBecomeAvailableAfterAPublish(final Sequencer sequencer) { final long seq = sequencer.next(); assertFalse(sequencer.isAvailable(seq)); sequencer.publish(seq); assertTrue(sequencer.isAvailable(seq)); } @ParameterizedTest @MethodSource("sequencerGenerator") void sequencesBecomeUnavailableAfterWrapping(final Sequencer sequencer) { final long seq = sequencer.next(); sequencer.publish(seq); assertTrue(sequencer.isAvailable(seq)); for (int i = 0; i < BUFFER_SIZE; i++) { sequencer.publish(sequencer.next()); } assertFalse(sequencer.isAvailable(seq)); } } ================================================ FILE: src/test/java/com/lmax/disruptor/ShutdownOnFatalExceptionTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.util.DaemonThreadFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import java.util.Random; import java.util.concurrent.TimeUnit; public class ShutdownOnFatalExceptionTest { private final Random random = new Random(); private final FailingEventHandler eventHandler = new FailingEventHandler(); private Disruptor disruptor; @SuppressWarnings("unchecked") @BeforeEach public void setUp() { disruptor = new Disruptor<>( new ByteArrayFactory(256), 1024, DaemonThreadFactory.INSTANCE, ProducerType.SINGLE, new BlockingWaitStrategy()); disruptor.handleEventsWith(eventHandler); disruptor.setDefaultExceptionHandler(new FatalExceptionHandler()); } @Test @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) public void shouldShutdownGracefulEvenWithFatalExceptionHandler() { disruptor.start(); byte[] bytes; for (int i = 1; i < 10; i++) { bytes = new byte[32]; random.nextBytes(bytes); disruptor.publishEvent(new ByteArrayTranslator(bytes)); } } @AfterEach public void tearDown() { disruptor.shutdown(); } private static class ByteArrayTranslator implements EventTranslator { private final byte[] bytes; ByteArrayTranslator(final byte[] bytes) { this.bytes = bytes; } @Override public void translateTo(final byte[] event, final long sequence) { System.arraycopy(bytes, 0, event, 0, bytes.length); } } private static class FailingEventHandler implements EventHandler { private int count = 0; @Override public void onEvent(final byte[] event, final long sequence, final boolean endOfBatch) throws Exception { // some logging count++; if (count == 3) { throw new IllegalStateException(); } } } private static class ByteArrayFactory implements EventFactory { private int eventSize; ByteArrayFactory(final int eventSize) { this.eventSize = eventSize; } @Override public byte[] newInstance() { return new byte[eventSize]; } } } ================================================ FILE: src/test/java/com/lmax/disruptor/SingleProducerSequencerTest.java ================================================ package com.lmax.disruptor; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsNot.not; public class SingleProducerSequencerTest { @Test public void shouldNotUpdateCursorDuringHasAvailableCapacity() throws Exception { SingleProducerSequencer sequencer = new SingleProducerSequencer(16, new BusySpinWaitStrategy()); for (int i = 0; i < 32; i++) { long next = sequencer.next(); assertThat(sequencer.cursor.get(), not(next)); sequencer.hasAvailableCapacity(13); assertThat(sequencer.cursor.get(), not(next)); sequencer.publish(next); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/SleepingWaitStrategyTest.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import org.junit.jupiter.api.Test; import static com.lmax.disruptor.support.WaitStrategyTestUtil.assertWaitForWithDelayOf; public class SleepingWaitStrategyTest { @Test public void shouldWaitForValue() throws Exception { assertWaitForWithDelayOf(50, new SleepingWaitStrategy()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/TimeoutBlockingWaitStrategyTest.java ================================================ package com.lmax.disruptor; import com.lmax.disruptor.support.DummySequenceBarrier; import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class TimeoutBlockingWaitStrategyTest { @Test public void shouldTimeoutWaitFor() { final SequenceBarrier sequenceBarrier = new DummySequenceBarrier(); long theTimeout = 500; TimeoutBlockingWaitStrategy waitStrategy = new TimeoutBlockingWaitStrategy(theTimeout, TimeUnit.MILLISECONDS); Sequence cursor = new Sequence(5); long t0 = System.currentTimeMillis(); assertThrows(TimeoutException.class, () -> waitStrategy.waitFor(6, cursor, cursor, sequenceBarrier)); long t1 = System.currentTimeMillis(); long timeWaiting = t1 - t0; assertTrue(timeWaiting >= theTimeout); } } ================================================ FILE: src/test/java/com/lmax/disruptor/YieldingWaitStrategyTest.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; import org.junit.jupiter.api.Test; import static com.lmax.disruptor.support.WaitStrategyTestUtil.assertWaitForWithDelayOf; public class YieldingWaitStrategyTest { @Test public void shouldWaitForValue() throws Exception { assertWaitForWithDelayOf(50, new YieldingWaitStrategy()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/MultiProducerSequencerUnsafe.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.alternatives; import com.lmax.disruptor.AbstractSequencer; import com.lmax.disruptor.InsufficientCapacityException; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.util.UnsafeAccess; import com.lmax.disruptor.util.Util; import sun.misc.Unsafe; import java.util.concurrent.locks.LockSupport; /** * Coordinator for claiming sequences for access to a data structure while tracking dependent {@link Sequence}s. * Suitable for use for sequencing across multiple publisher threads.

* *

* Note on {@link Sequencer#getCursor()}: With this sequencer the cursor value is updated after the call * to {@link Sequencer#next()}, to determine the highest available sequence that can be read, then * {@link Sequencer#getHighestPublishedSequence(long, long)} should be used.

*/ public final class MultiProducerSequencerUnsafe extends AbstractSequencer { private static final Unsafe UNSAFE = UnsafeAccess.getUnsafe(); private static final long BASE = UNSAFE.arrayBaseOffset(int[].class); private static final long SCALE = UNSAFE.arrayIndexScale(int[].class); private final Sequence gatingSequenceCache = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); // availableBuffer tracks the state of each ringbuffer slot // see below for more details on the approach private final int[] availableBuffer; private final int indexMask; private final int indexShift; /** * Construct a Sequencer with the selected wait strategy and buffer size. * * @param bufferSize the size of the buffer that this will sequence over. * @param waitStrategy for those waiting on sequences. */ public MultiProducerSequencerUnsafe(final int bufferSize, final WaitStrategy waitStrategy) { super(bufferSize, waitStrategy); availableBuffer = new int[bufferSize]; indexMask = bufferSize - 1; indexShift = Util.log2(bufferSize); initialiseAvailableBuffer(); } /** * @see Sequencer#hasAvailableCapacity(int) */ @Override public boolean hasAvailableCapacity(final int requiredCapacity) { return hasAvailableCapacity(gatingSequences, requiredCapacity, cursor.get()); } private boolean hasAvailableCapacity(final Sequence[] gatingSequences, final int requiredCapacity, final long cursorValue) { long wrapPoint = (cursorValue + requiredCapacity) - bufferSize; long cachedGatingSequence = gatingSequenceCache.get(); if (wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue) { long minSequence = Util.getMinimumSequence(gatingSequences, cursorValue); gatingSequenceCache.set(minSequence); if (wrapPoint > minSequence) { return false; } } return true; } /** * @see Sequencer#claim(long) */ @Override public void claim(final long sequence) { cursor.set(sequence); } /** * @see Sequencer#next() */ @Override public long next() { return next(1); } /** * @see Sequencer#next(int) */ @Override public long next(final int n) { if (n < 1 || n > bufferSize) { throw new IllegalArgumentException("n must be > 0 and < bufferSize"); } long current; long next; do { current = cursor.get(); next = current + n; long wrapPoint = next - bufferSize; long cachedGatingSequence = gatingSequenceCache.get(); if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current) { long gatingSequence = Util.getMinimumSequence(gatingSequences, current); if (wrapPoint > gatingSequence) { LockSupport.parkNanos(1); // TODO, should we spin based on the wait strategy? continue; } gatingSequenceCache.set(gatingSequence); } else if (cursor.compareAndSet(current, next)) { break; } } while (true); return next; } /** * @see Sequencer#tryNext() */ @Override public long tryNext() throws InsufficientCapacityException { return tryNext(1); } /** * @see Sequencer#tryNext(int) */ @Override public long tryNext(final int n) throws InsufficientCapacityException { if (n < 1) { throw new IllegalArgumentException("n must be > 0"); } long current; long next; do { current = cursor.get(); next = current + n; if (!hasAvailableCapacity(gatingSequences, n, current)) { throw InsufficientCapacityException.INSTANCE; } } while (!cursor.compareAndSet(current, next)); return next; } /** * @see Sequencer#remainingCapacity() */ @Override public long remainingCapacity() { long consumed = Util.getMinimumSequence(gatingSequences, cursor.get()); long produced = cursor.get(); return getBufferSize() - (produced - consumed); } private void initialiseAvailableBuffer() { for (int i = availableBuffer.length - 1; i != 0; i--) { setAvailableBufferValue(i, -1); } setAvailableBufferValue(0, -1); } /** * @see Sequencer#publish(long) */ @Override public void publish(final long sequence) { setAvailable(sequence); waitStrategy.signalAllWhenBlocking(); } /** * @see Sequencer#publish(long, long) */ @Override public void publish(final long lo, final long hi) { for (long l = lo; l <= hi; l++) { setAvailable(l); } waitStrategy.signalAllWhenBlocking(); } /** * The below methods work on the availableBuffer flag. * *

The prime reason is to avoid a shared sequence object between publisher threads. * (Keeping single pointers tracking start and end would require coordination * between the threads). * *

-- Firstly we have the constraint that the delta between the cursor and minimum * gating sequence will never be larger than the buffer size (the code in * next/tryNext in the Sequence takes care of that). * -- Given that; take the sequence value and mask off the lower portion of the * sequence as the index into the buffer (indexMask). (aka modulo operator) * -- The upper portion of the sequence becomes the value to check for availability. * ie: it tells us how many times around the ring buffer we've been (aka division) * -- Because we can't wrap without the gating sequences moving forward (i.e. the * minimum gating sequence is effectively our last available position in the * buffer), when we have new data and successfully claimed a slot we can simply * write over the top. */ private void setAvailable(final long sequence) { setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence)); } private void setAvailableBufferValue(final int index, final int flag) { long bufferAddress = (index * SCALE) + BASE; UNSAFE.putOrderedInt(availableBuffer, bufferAddress, flag); } /** * @see Sequencer#isAvailable(long) */ @Override public boolean isAvailable(final long sequence) { int index = calculateIndex(sequence); int flag = calculateAvailabilityFlag(sequence); long bufferAddress = (index * SCALE) + BASE; return UNSAFE.getIntVolatile(availableBuffer, bufferAddress) == flag; } @Override public long getHighestPublishedSequence(final long lowerBound, final long availableSequence) { for (long sequence = lowerBound; sequence <= availableSequence; sequence++) { if (!isAvailable(sequence)) { return sequence - 1; } } return availableSequence; } private int calculateAvailabilityFlag(final long sequence) { return (int) (sequence >>> indexShift); } private int calculateIndex(final long sequence) { return ((int) sequence) & indexMask; } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/MultiProducerSequencerVarHandle.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.alternatives; import com.lmax.disruptor.AbstractSequencer; import com.lmax.disruptor.InsufficientCapacityException; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.util.Util; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.concurrent.locks.LockSupport; /** * Coordinator for claiming sequences for access to a data structure while tracking dependent {@link Sequence}s. * Suitable for use for sequencing across multiple publisher threads.

* *

* Note on {@link Sequencer#getCursor()}: With this sequencer the cursor value is updated after the call * to {@link Sequencer#next()}, to determine the highest available sequence that can be read, then * {@link Sequencer#getHighestPublishedSequence(long, long)} should be used.

*/ public final class MultiProducerSequencerVarHandle extends AbstractSequencer { private static final VarHandle AVAILABLE_ARRAY = MethodHandles.arrayElementVarHandle(int[].class); private final Sequence gatingSequenceCache = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); // availableBuffer tracks the state of each ringbuffer slot // see below for more details on the approach private final int[] availableBuffer; private final int indexMask; private final int indexShift; /** * Construct a Sequencer with the selected wait strategy and buffer size. * * @param bufferSize the size of the buffer that this will sequence over. * @param waitStrategy for those waiting on sequences. */ public MultiProducerSequencerVarHandle(final int bufferSize, final WaitStrategy waitStrategy) { super(bufferSize, waitStrategy); availableBuffer = new int[bufferSize]; indexMask = bufferSize - 1; indexShift = Util.log2(bufferSize); initialiseAvailableBuffer(); } /** * @see Sequencer#hasAvailableCapacity(int) */ @Override public boolean hasAvailableCapacity(final int requiredCapacity) { return hasAvailableCapacity(gatingSequences, requiredCapacity, cursor.get()); } private boolean hasAvailableCapacity(final Sequence[] gatingSequences, final int requiredCapacity, final long cursorValue) { long wrapPoint = (cursorValue + requiredCapacity) - bufferSize; long cachedGatingSequence = gatingSequenceCache.get(); if (wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue) { long minSequence = Util.getMinimumSequence(gatingSequences, cursorValue); gatingSequenceCache.set(minSequence); if (wrapPoint > minSequence) { return false; } } return true; } /** * @see Sequencer#claim(long) */ @Override public void claim(final long sequence) { cursor.set(sequence); } /** * @see Sequencer#next() */ @Override public long next() { return next(1); } /** * @see Sequencer#next(int) */ @Override public long next(final int n) { if (n < 1 || n > bufferSize) { throw new IllegalArgumentException("n must be > 0 and < bufferSize"); } long current; long next; do { current = cursor.get(); next = current + n; long wrapPoint = next - bufferSize; long cachedGatingSequence = gatingSequenceCache.get(); if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current) { long gatingSequence = Util.getMinimumSequence(gatingSequences, current); if (wrapPoint > gatingSequence) { LockSupport.parkNanos(1); // TODO, should we spin based on the wait strategy? continue; } gatingSequenceCache.set(gatingSequence); } else if (cursor.compareAndSet(current, next)) { break; } } while (true); return next; } /** * @see Sequencer#tryNext() */ @Override public long tryNext() throws InsufficientCapacityException { return tryNext(1); } /** * @see Sequencer#tryNext(int) */ @Override public long tryNext(final int n) throws InsufficientCapacityException { if (n < 1) { throw new IllegalArgumentException("n must be > 0"); } long current; long next; do { current = cursor.get(); next = current + n; if (!hasAvailableCapacity(gatingSequences, n, current)) { throw InsufficientCapacityException.INSTANCE; } } while (!cursor.compareAndSet(current, next)); return next; } /** * @see Sequencer#remainingCapacity() */ @Override public long remainingCapacity() { long consumed = Util.getMinimumSequence(gatingSequences, cursor.get()); long produced = cursor.get(); return getBufferSize() - (produced - consumed); } private void initialiseAvailableBuffer() { for (int i = availableBuffer.length - 1; i != 0; i--) { setAvailableBufferValue(i, -1); } setAvailableBufferValue(0, -1); } /** * @see Sequencer#publish(long) */ @Override public void publish(final long sequence) { setAvailable(sequence); waitStrategy.signalAllWhenBlocking(); } /** * @see Sequencer#publish(long, long) */ @Override public void publish(final long lo, final long hi) { for (long l = lo; l <= hi; l++) { setAvailable(l); } waitStrategy.signalAllWhenBlocking(); } /** * The below methods work on the availableBuffer flag. * *

The prime reason is to avoid a shared sequence object between publisher threads. * (Keeping single pointers tracking start and end would require coordination * between the threads). * *

-- Firstly we have the constraint that the delta between the cursor and minimum * gating sequence will never be larger than the buffer size (the code in * next/tryNext in the Sequence takes care of that). * -- Given that; take the sequence value and mask off the lower portion of the * sequence as the index into the buffer (indexMask). (aka modulo operator) * -- The upper portion of the sequence becomes the value to check for availability. * ie: it tells us how many times around the ring buffer we've been (aka division) * -- Because we can't wrap without the gating sequences moving forward (i.e. the * minimum gating sequence is effectively our last available position in the * buffer), when we have new data and successfully claimed a slot we can simply * write over the top. */ private void setAvailable(final long sequence) { setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence)); } private void setAvailableBufferValue(final int index, final int flag) { AVAILABLE_ARRAY.setRelease(availableBuffer, index, flag); } /** * @see Sequencer#isAvailable(long) */ @Override public boolean isAvailable(final long sequence) { int index = calculateIndex(sequence); int flag = calculateAvailabilityFlag(sequence); return (int) AVAILABLE_ARRAY.getAcquire(availableBuffer, index) == flag; } @Override public long getHighestPublishedSequence(final long lowerBound, final long availableSequence) { for (long sequence = lowerBound; sequence <= availableSequence; sequence++) { if (!isAvailable(sequence)) { return sequence - 1; } } return availableSequence; } private int calculateAvailabilityFlag(final long sequence) { return (int) (sequence >>> indexShift); } private int calculateIndex(final long sequence) { return ((int) sequence) & indexMask; } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/RingBufferArray.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.alternatives; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.Cursored; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventPoller; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.EventSequencer; import com.lmax.disruptor.EventSink; import com.lmax.disruptor.EventTranslator; import com.lmax.disruptor.EventTranslatorOneArg; import com.lmax.disruptor.EventTranslatorThreeArg; import com.lmax.disruptor.EventTranslatorTwoArg; import com.lmax.disruptor.EventTranslatorVararg; import com.lmax.disruptor.InsufficientCapacityException; import com.lmax.disruptor.MultiProducerSequencer; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.SingleProducerSequencer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.dsl.ProducerType; abstract class RingBufferPad { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; } abstract class RingBufferFieldsArray extends RingBufferPad { private static final int BUFFER_PAD = 32; private final long indexMask; private final E[] entries; protected final int bufferSize; protected final Sequencer sequencer; @SuppressWarnings("unchecked") RingBufferFieldsArray( final EventFactory eventFactory, final Sequencer sequencer) { this.sequencer = sequencer; this.bufferSize = sequencer.getBufferSize(); if (bufferSize < 1) { throw new IllegalArgumentException("bufferSize must not be less than 1"); } if (Integer.bitCount(bufferSize) != 1) { throw new IllegalArgumentException("bufferSize must be a power of 2"); } this.indexMask = bufferSize - 1; this.entries = (E[]) new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD]; fill(eventFactory); } private void fill(final EventFactory eventFactory) { for (int i = 0; i < bufferSize; i++) { entries[BUFFER_PAD + i] = eventFactory.newInstance(); } } protected final E elementAt(final long sequence) { return entries[BUFFER_PAD + (int) (sequence & indexMask)]; } } /** * Ring based store of reusable entries containing the data representing * an event being exchanged between event producer and {@link EventProcessor}s. * * @param implementation storing the data for sharing during exchange or parallel coordination of an event. */ public final class RingBufferArray extends RingBufferFieldsArray implements Cursored, EventSequencer, EventSink { /** * The initial cursor value */ public static final long INITIAL_CURSOR_VALUE = SequenceVarHandle.INITIAL_VALUE; protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; /** * Construct a RingBuffer with the full option set. * * @param eventFactory to newInstance entries for filling the RingBuffer * @param sequencer sequencer to handle the ordering of events moving through the RingBuffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 */ public RingBufferArray( final EventFactory eventFactory, final Sequencer sequencer) { super(eventFactory, sequencer); } /** * Create a new multiple producer RingBuffer with the specified wait strategy. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBufferArray createMultiProducer( final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { MultiProducerSequencer sequencer = new MultiProducerSequencer(bufferSize, waitStrategy); return new RingBufferArray<>(factory, sequencer); } /** * Create a new multiple producer RingBuffer using the default wait strategy {@link BlockingWaitStrategy}. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBufferArray createMultiProducer(final EventFactory factory, final int bufferSize) { return createMultiProducer(factory, bufferSize, new BlockingWaitStrategy()); } /** * Create a new single producer RingBuffer with the specified wait strategy. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see SingleProducerSequencer */ public static RingBufferArray createSingleProducer( final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy); return new RingBufferArray<>(factory, sequencer); } /** * Create a new single producer RingBuffer using the default wait strategy {@link BlockingWaitStrategy}. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBufferArray createSingleProducer(final EventFactory factory, final int bufferSize) { return createSingleProducer(factory, bufferSize, new BlockingWaitStrategy()); } /** * Create a new Ring Buffer with the specified producer type (SINGLE or MULTI) * * @param Class of the event stored in the ring buffer. * @param producerType producer type to use {@link ProducerType}. * @param factory used to create events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 */ public static RingBufferArray create( final ProducerType producerType, final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { switch (producerType) { case SINGLE: return createSingleProducer(factory, bufferSize, waitStrategy); case MULTI: return createMultiProducer(factory, bufferSize, waitStrategy); default: throw new IllegalStateException(producerType.toString()); } } /** *

Get the event for a given sequence in the RingBuffer.

* *

This call has 2 uses. Firstly use this call when publishing to a ring buffer. * After calling {@link RingBufferArray#next()} use this call to get hold of the * preallocated event to fill with data before calling {@link RingBufferArray#publish(long)}.

* *

Secondly use this call when consuming data from the ring buffer. After calling * {@link SequenceBarrier#waitFor(long)} call this method with any value greater than * that your current consumer sequence and less than or equal to the value returned from * the {@link SequenceBarrier#waitFor(long)} method.

* * @param sequence for the event * @return the event for the given sequence */ @Override public E get(final long sequence) { return elementAt(sequence); } /** * Increment and return the next sequence for the ring buffer. Calls of this * method should ensure that they always publish the sequence afterward. E.g. *
     * long sequence = ringBuffer.next();
     * try {
     *     Event e = ringBuffer.get(sequence);
     *     // Do some work with the event.
     * } finally {
     *     ringBuffer.publish(sequence);
     * }
     * 
* * @return The next sequence to publish to. * @see RingBufferArray#publish(long) * @see RingBufferArray#get(long) */ @Override public long next() { return sequencer.next(); } /** * The same functionality as {@link RingBufferArray#next()}, but allows the caller to claim * the next n sequences. * * @param n number of slots to claim * @return sequence number of the highest slot claimed * @see Sequencer#next(int) */ @Override public long next(final int n) { return sequencer.next(n); } /** *

Increment and return the next sequence for the ring buffer. Calls of this * method should ensure that they always publish the sequence afterward. E.g.

*
     * long sequence = ringBuffer.next();
     * try {
     *     Event e = ringBuffer.get(sequence);
     *     // Do some work with the event.
     * } finally {
     *     ringBuffer.publish(sequence);
     * }
     * 
*

This method will not block if there is not space available in the ring * buffer, instead it will throw an {@link InsufficientCapacityException}.

* * @return The next sequence to publish to. * @throws InsufficientCapacityException if the necessary space in the ring buffer is not available * @see RingBufferArray#publish(long) * @see RingBufferArray#get(long) */ @Override public long tryNext() throws InsufficientCapacityException { return sequencer.tryNext(); } /** * The same functionality as {@link RingBufferArray#tryNext()}, but allows the caller to attempt * to claim the next n sequences. * * @param n number of slots to claim * @return sequence number of the highest slot claimed * @throws InsufficientCapacityException if the necessary space in the ring buffer is not available */ @Override public long tryNext(final int n) throws InsufficientCapacityException { return sequencer.tryNext(n); } /** * Resets the cursor to a specific value. This can be applied at any time, but it is worth noting * that it can cause a data race and should only be used in controlled circumstances. E.g. during * initialisation. * * @param sequence The sequence to reset too. * @throws IllegalStateException If any gating sequences have already been specified. */ @Deprecated public void resetTo(final long sequence) { sequencer.claim(sequence); sequencer.publish(sequence); } /** * Sets the cursor to a specific sequence and returns the preallocated entry that is stored there. This * can cause a data race and should only be done in controlled circumstances, e.g. during initialisation. * * @param sequence The sequence to claim. * @return The preallocated event. */ public E claimAndGetPreallocated(final long sequence) { sequencer.claim(sequence); return get(sequence); } /** * Determines if the event for a given sequence is currently available. * *

Note that this does not guarantee that event will still be available * on the next interaction with the RingBuffer. For example, it is not * necessarily safe to write code like this: * *

{@code
     * if (ringBuffer.isAvailable(sequence))
     * {
     *     final E e = ringBuffer.get(sequence);
     *     // ...do something with e
     * }
     * }
* *

because there is a race between the reading thread and the writing thread. * *

This method will also return false when querying for sequences that are * behind the ring buffer's wrap point. * * @param sequence The sequence to identify the entry. * @return If the event published with the given sequence number is currently available. */ public boolean isAvailable(final long sequence) { return sequencer.isAvailable(sequence); } /** * Add the specified gating sequences to this instance of the Disruptor. They will * safely and atomically added to the list of gating sequences. * * @param gatingSequences The sequences to add. */ public void addGatingSequences(final Sequence... gatingSequences) { sequencer.addGatingSequences(gatingSequences); } /** * Get the minimum sequence value from all of the gating sequences * added to this ringBuffer. * * @return The minimum gating sequence or the cursor sequence if * no sequences have been added. */ public long getMinimumGatingSequence() { return sequencer.getMinimumSequence(); } /** * Remove the specified sequence from this ringBuffer. * * @param sequence to be removed. * @return true if this sequence was found, false otherwise. */ public boolean removeGatingSequence(final Sequence sequence) { return sequencer.removeGatingSequence(sequence); } /** * Create a new SequenceBarrier to be used by an EventProcessor to track which messages * are available to be read from the ring buffer given a list of sequences to track. * * @param sequencesToTrack the additional sequences to track * @return A sequence barrier that will track the specified sequences. * @see SequenceBarrier */ public SequenceBarrier newBarrier(final Sequence... sequencesToTrack) { return sequencer.newBarrier(sequencesToTrack); } /** * Creates an event poller for this ring buffer gated on the supplied sequences. * * @param gatingSequences to be gated on. * @return A poller that will gate on this ring buffer and the supplied sequences. */ public EventPoller newPoller(final Sequence... gatingSequences) { return sequencer.newPoller(this, gatingSequences); } /** * Get the current cursor value for the ring buffer. The actual value received * will depend on the type of {@link Sequencer} that is being used. * * @see MultiProducerSequencer * @see SingleProducerSequencer */ @Override public long getCursor() { return sequencer.getCursor(); } /** * The size of the buffer. * * @return size of buffer */ @Override public int getBufferSize() { return bufferSize; } /** * Given specified requiredCapacity determines if that amount of space * is available. Note, you can not assume that if this method returns true * that a call to {@link RingBufferArray#next()} will not block. Especially true if this * ring buffer is set up to handle multiple producers. * * @param requiredCapacity The capacity to check for. * @return true If the specified requiredCapacity is available * false if not. */ @Override public boolean hasAvailableCapacity(final int requiredCapacity) { return sequencer.hasAvailableCapacity(requiredCapacity); } /** * @see EventSink#publishEvent(EventTranslator) */ @Override public void publishEvent(final EventTranslator translator) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence); } /** * @see EventSink#tryPublishEvent(EventTranslator) */ @Override public boolean tryPublishEvent(final EventTranslator translator) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvent(EventTranslatorOneArg, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorOneArg, A) */ @Override public void publishEvent(final EventTranslatorOneArg translator, final A arg0) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0); } /** * @see EventSink#tryPublishEvent(EventTranslatorOneArg, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorOneArg, A) */ @Override public boolean tryPublishEvent(final EventTranslatorOneArg translator, final A arg0) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvent(EventTranslatorTwoArg, Object, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorTwoArg, A, B) */ @Override public void publishEvent(final EventTranslatorTwoArg translator, final A arg0, final B arg1) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0, arg1); } /** * @see EventSink#tryPublishEvent(EventTranslatorTwoArg, Object, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorTwoArg, A, B) */ @Override public boolean tryPublishEvent(final EventTranslatorTwoArg translator, final A arg0, final B arg1) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0, arg1); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvent(EventTranslatorThreeArg, Object, Object, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorThreeArg, A, B, C) */ @Override public void publishEvent(final EventTranslatorThreeArg translator, final A arg0, final B arg1, final C arg2) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0, arg1, arg2); } /** * @see EventSink#tryPublishEvent(EventTranslatorThreeArg, Object, Object, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorThreeArg, A, B, C) */ @Override public boolean tryPublishEvent(final EventTranslatorThreeArg translator, final A arg0, final B arg1, final C arg2) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0, arg1, arg2); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvent(EventTranslatorVararg, Object...) */ @Override public void publishEvent(final EventTranslatorVararg translator, final Object... args) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, args); } /** * @see EventSink#tryPublishEvent(EventTranslatorVararg, Object...) */ @Override public boolean tryPublishEvent(final EventTranslatorVararg translator, final Object... args) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, args); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvents(EventTranslator[]) */ @Override public void publishEvents(final EventTranslator[] translators) { publishEvents(translators, 0, translators.length); } /** * @see EventSink#publishEvents(EventTranslator[], int, int) */ @Override public void publishEvents(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBounds(translators, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translators, batchStartsAt, batchSize, finalSequence); } /** * @see EventSink#tryPublishEvents(EventTranslator[]) */ @Override public boolean tryPublishEvents(final EventTranslator[] translators) { return tryPublishEvents(translators, 0, translators.length); } /** * @see EventSink#tryPublishEvents(EventTranslator[], int, int) */ @Override public boolean tryPublishEvents(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBounds(translators, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translators, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvents(EventTranslatorOneArg, Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, A[]) */ @Override public void publishEvents(final EventTranslatorOneArg translator, final A[] arg0) { publishEvents(translator, 0, arg0.length, arg0); } /** * @see EventSink#publishEvents(EventTranslatorOneArg, int, int, Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, A[]) */ @Override public void publishEvents(final EventTranslatorOneArg translator, final int batchStartsAt, final int batchSize, final A[] arg0) { checkBounds(arg0, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, batchStartsAt, batchSize, finalSequence); } /** * @see EventSink#tryPublishEvents(EventTranslatorOneArg, Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, A[]) */ @Override public boolean tryPublishEvents(final EventTranslatorOneArg translator, final A[] arg0) { return tryPublishEvents(translator, 0, arg0.length, arg0); } /** * @see EventSink#tryPublishEvents(EventTranslatorOneArg, int, int, Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, A[]) */ @Override public boolean tryPublishEvents( final EventTranslatorOneArg translator, final int batchStartsAt, final int batchSize, final A[] arg0) { checkBounds(arg0, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvents(EventTranslatorTwoArg, Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, A[], B[]) */ @Override public void publishEvents(final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1) { publishEvents(translator, 0, arg0.length, arg0, arg1); } /** * @see EventSink#publishEvents(EventTranslatorTwoArg, int, int, Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, A[], B[]) */ @Override public void publishEvents( final EventTranslatorTwoArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1) { checkBounds(arg0, arg1, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, arg1, batchStartsAt, batchSize, finalSequence); } /** * @see EventSink#tryPublishEvents(EventTranslatorTwoArg, Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, A[], B[]) */ @Override public boolean tryPublishEvents(final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1) { return tryPublishEvents(translator, 0, arg0.length, arg0, arg1); } /** * @see EventSink#tryPublishEvents(EventTranslatorTwoArg, int, int, Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, A[], B[]) */ @Override public boolean tryPublishEvents( final EventTranslatorTwoArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1) { checkBounds(arg0, arg1, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, arg1, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvents(EventTranslatorThreeArg, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, A[], B[], C[]) */ @Override public void publishEvents(final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2) { publishEvents(translator, 0, arg0.length, arg0, arg1, arg2); } /** * @see EventSink#publishEvents(EventTranslatorThreeArg, int, int, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, A[], B[], C[]) */ @Override public void publishEvents( final EventTranslatorThreeArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1, final C[] arg2) { checkBounds(arg0, arg1, arg2, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, arg1, arg2, batchStartsAt, batchSize, finalSequence); } /** * @see EventSink#tryPublishEvents(EventTranslatorThreeArg, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, A[], B[], C[]) */ @Override public boolean tryPublishEvents( final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2) { return tryPublishEvents(translator, 0, arg0.length, arg0, arg1, arg2); } /** * @see EventSink#tryPublishEvents(EventTranslatorThreeArg, int, int, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, A[], B[], C[]) */ @Override public boolean tryPublishEvents( final EventTranslatorThreeArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1, final C[] arg2) { checkBounds(arg0, arg1, arg2, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, arg1, arg2, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see EventSink#publishEvents(EventTranslatorVararg, Object[][]) */ @Override public void publishEvents(final EventTranslatorVararg translator, final Object[]... args) { publishEvents(translator, 0, args.length, args); } /** * @see EventSink#publishEvents(EventTranslatorVararg, int, int, Object[][]) */ @Override public void publishEvents(final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final Object[]... args) { checkBounds(batchStartsAt, batchSize, args); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, batchStartsAt, batchSize, finalSequence, args); } /** * @see EventSink#tryPublishEvents(EventTranslatorVararg, Object[][]) */ @Override public boolean tryPublishEvents(final EventTranslatorVararg translator, final Object[]... args) { return tryPublishEvents(translator, 0, args.length, args); } /** * @see EventSink#tryPublishEvents(EventTranslatorVararg, int, int, Object[][]) */ @Override public boolean tryPublishEvents( final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final Object[]... args) { checkBounds(args, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, batchStartsAt, batchSize, finalSequence, args); return true; } catch (InsufficientCapacityException e) { return false; } } /** * Publish the specified sequence. This action marks this particular * message as being available to be read. * * @param sequence the sequence to publish. */ @Override public void publish(final long sequence) { sequencer.publish(sequence); } /** * Publish the specified sequences. This action marks these particular * messages as being available to be read. * * @param lo the lowest sequence number to be published * @param hi the highest sequence number to be published * @see Sequencer#next(int) */ @Override public void publish(final long lo, final long hi) { sequencer.publish(lo, hi); } /** * Get the remaining capacity for this ringBuffer. * * @return The number of slots remaining. */ @Override public long remainingCapacity() { return sequencer.remainingCapacity(); } private void checkBounds(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(translators, batchStartsAt, batchSize); } private void checkBatchSizing(final int batchStartsAt, final int batchSize) { if (batchStartsAt < 0 || batchSize < 0) { throw new IllegalArgumentException("Both batchStartsAt and batchSize must be positive but got: batchStartsAt " + batchStartsAt + " and batchSize " + batchSize); } else if (batchSize > bufferSize) { throw new IllegalArgumentException("The ring buffer cannot accommodate " + batchSize + " it only has space for " + bufferSize + " entities."); } } private void checkBounds(final A[] arg0, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); } private void checkBounds(final A[] arg0, final B[] arg1, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); batchOverRuns(arg1, batchStartsAt, batchSize); } private void checkBounds( final A[] arg0, final B[] arg1, final C[] arg2, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); batchOverRuns(arg1, batchStartsAt, batchSize); batchOverRuns(arg2, batchStartsAt, batchSize); } private void checkBounds(final int batchStartsAt, final int batchSize, final Object[][] args) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(args, batchStartsAt, batchSize); } private void batchOverRuns(final A[] arg0, final int batchStartsAt, final int batchSize) { if (batchStartsAt + batchSize > arg0.length) { throw new IllegalArgumentException( "A batchSize of: " + batchSize + " with batchStatsAt of: " + batchStartsAt + " will overrun the available number of arguments: " + (arg0.length - batchStartsAt)); } } private void translateAndPublish(final EventTranslator translator, final long sequence) { try { translator.translateTo(get(sequence), sequence); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorOneArg translator, final long sequence, final A arg0) { try { translator.translateTo(get(sequence), sequence, arg0); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorTwoArg translator, final long sequence, final A arg0, final B arg1) { try { translator.translateTo(get(sequence), sequence, arg0, arg1); } finally { sequencer.publish(sequence); } } private void translateAndPublish( final EventTranslatorThreeArg translator, final long sequence, final A arg0, final B arg1, final C arg2) { try { translator.translateTo(get(sequence), sequence, arg0, arg1, arg2); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorVararg translator, final long sequence, final Object... args) { try { translator.translateTo(get(sequence), sequence, args); } finally { sequencer.publish(sequence); } } private void translateAndPublishBatch( final EventTranslator[] translators, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { final EventTranslator translator = translators[i]; translator.translateTo(get(sequence), sequence++); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorOneArg translator, final A[] arg0, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i], arg1[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i], arg1[i], arg2[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final long finalSequence, final Object[][] args) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, args[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } @Override public String toString() { return "RingBuffer{" + "bufferSize=" + bufferSize + ", sequencer=" + sequencer + "}"; } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/RingBufferUnsafe.java ================================================ package com.lmax.disruptor.alternatives; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.Cursored; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventPoller; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.EventSequencer; import com.lmax.disruptor.EventSink; import com.lmax.disruptor.EventTranslator; import com.lmax.disruptor.EventTranslatorOneArg; import com.lmax.disruptor.EventTranslatorThreeArg; import com.lmax.disruptor.EventTranslatorTwoArg; import com.lmax.disruptor.EventTranslatorVararg; import com.lmax.disruptor.InsufficientCapacityException; import com.lmax.disruptor.MultiProducerSequencer; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.SingleProducerSequencer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.dsl.ProducerType; import com.lmax.disruptor.util.UnsafeAccess; import sun.misc.Unsafe; abstract class RingBufferPadUnsafe { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; } abstract class RingBufferFieldsUnsafe extends RingBufferPadUnsafe { private static final int BUFFER_PAD; private static final long REF_ARRAY_BASE; private static final int REF_ELEMENT_SHIFT; private static final Unsafe UNSAFE = UnsafeAccess.getUnsafe(); private static final int POINTER_SIZE_32_BIT = 4; private static final int BITSHIFT_MULTIPLIER_FOUR = 2; public static final int POINTER_SIZE_64_BIT = 8; private static final int BITSHIFT_MULTIPLIER_EIGHT = 3; private static final int BUFFER_PADDING_BYTES = 128; static { final int scale = UNSAFE.arrayIndexScale(Object[].class); if (POINTER_SIZE_32_BIT == scale) { REF_ELEMENT_SHIFT = BITSHIFT_MULTIPLIER_FOUR; } else if (POINTER_SIZE_64_BIT == scale) { REF_ELEMENT_SHIFT = BITSHIFT_MULTIPLIER_EIGHT; } else { throw new IllegalStateException("Unknown pointer size"); } BUFFER_PAD = BUFFER_PADDING_BYTES / scale; // Including the buffer pad in the array base offset REF_ARRAY_BASE = UNSAFE.arrayBaseOffset(Object[].class) + BUFFER_PADDING_BYTES; } private final long indexMask; private final Object[] entries; protected final int bufferSize; protected final Sequencer sequencer; RingBufferFieldsUnsafe( final EventFactory eventFactory, final Sequencer sequencer) { this.sequencer = sequencer; this.bufferSize = sequencer.getBufferSize(); if (bufferSize < 1) { throw new IllegalArgumentException("bufferSize must not be less than 1"); } if (Integer.bitCount(bufferSize) != 1) { throw new IllegalArgumentException("bufferSize must be a power of 2"); } this.indexMask = bufferSize - 1; this.entries = new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD]; fill(eventFactory); } private void fill(final EventFactory eventFactory) { for (int i = 0; i < bufferSize; i++) { entries[BUFFER_PAD + i] = eventFactory.newInstance(); } } @SuppressWarnings("unchecked") protected final E elementAt(final long sequence) { return (E) UNSAFE.getObject(entries, REF_ARRAY_BASE + ((sequence & indexMask) << REF_ELEMENT_SHIFT)); } } /** * Ring based store of reusable entries containing the data representing * an event being exchanged between event producer and {@link EventProcessor}s. * * @param implementation storing the data for sharing during exchange or parallel coordination of an event. */ public final class RingBufferUnsafe extends RingBufferFieldsUnsafe implements Cursored, EventSequencer, EventSink { public static final long INITIAL_CURSOR_VALUE = SequenceUnsafe.INITIAL_VALUE; protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; /** * Construct a RingBuffer with the full option set. * * @param eventFactory to newInstance entries for filling the RingBuffer * @param sequencer sequencer to handle the ordering of events moving through the RingBuffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 */ public RingBufferUnsafe( final EventFactory eventFactory, final Sequencer sequencer) { super(eventFactory, sequencer); } /** * Create a new multiple producer RingBuffer with the specified wait strategy. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBufferUnsafe createMultiProducer( final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { MultiProducerSequencer sequencer = new MultiProducerSequencer(bufferSize, waitStrategy); return new RingBufferUnsafe<>(factory, sequencer); } /** * Create a new multiple producer RingBuffer using the default wait strategy {@link BlockingWaitStrategy}. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBufferUnsafe createMultiProducer(final EventFactory factory, final int bufferSize) { return createMultiProducer(factory, bufferSize, new BlockingWaitStrategy()); } /** * Create a new single producer RingBuffer with the specified wait strategy. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see SingleProducerSequencer */ public static RingBufferUnsafe createSingleProducer( final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy); return new RingBufferUnsafe<>(factory, sequencer); } /** * Create a new single producer RingBuffer using the default wait strategy {@link BlockingWaitStrategy}. * * @param Class of the event stored in the ring buffer. * @param factory used to create the events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 * @see MultiProducerSequencer */ public static RingBufferUnsafe createSingleProducer(final EventFactory factory, final int bufferSize) { return createSingleProducer(factory, bufferSize, new BlockingWaitStrategy()); } /** * Create a new Ring Buffer with the specified producer type (SINGLE or MULTI) * * @param Class of the event stored in the ring buffer. * @param producerType producer type to use {@link ProducerType}. * @param factory used to create events within the ring buffer. * @param bufferSize number of elements to create within the ring buffer. * @param waitStrategy used to determine how to wait for new elements to become available. * @return a constructed ring buffer. * @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2 */ public static RingBufferUnsafe create( final ProducerType producerType, final EventFactory factory, final int bufferSize, final WaitStrategy waitStrategy) { switch (producerType) { case SINGLE: return createSingleProducer(factory, bufferSize, waitStrategy); case MULTI: return createMultiProducer(factory, bufferSize, waitStrategy); default: throw new IllegalStateException(producerType.toString()); } } /** *

Get the event for a given sequence in the RingBuffer.

* *

This call has 2 uses. Firstly use this call when publishing to a ring buffer. * After calling {@link com.lmax.disruptor.RingBuffer#next()} use this call to get hold of the * preallocated event to fill with data before calling {@link com.lmax.disruptor.RingBuffer#publish(long)}.

* *

Secondly use this call when consuming data from the ring buffer. After calling * {@link SequenceBarrier#waitFor(long)} call this method with any value greater than * that your current consumer sequence and less than or equal to the value returned from * the {@link SequenceBarrier#waitFor(long)} method.

* * @param sequence for the event * @return the event for the given sequence */ @Override public E get(final long sequence) { return elementAt(sequence); } /** * Increment and return the next sequence for the ring buffer. Calls of this * method should ensure that they always publish the sequence afterward. E.g. *
     * long sequence = ringBuffer.next();
     * try {
     *     Event e = ringBuffer.get(sequence);
     *     // Do some work with the event.
     * } finally {
     *     ringBuffer.publish(sequence);
     * }
     * 
* * @return The next sequence to publish to. * @see com.lmax.disruptor.RingBuffer#publish(long) * @see com.lmax.disruptor.RingBuffer#get(long) */ @Override public long next() { return sequencer.next(); } /** * The same functionality as {@link com.lmax.disruptor.RingBuffer#next()}, but allows the caller to claim * the next n sequences. * * @param n number of slots to claim * @return sequence number of the highest slot claimed * @see Sequencer#next(int) */ @Override public long next(final int n) { return sequencer.next(n); } /** *

Increment and return the next sequence for the ring buffer. Calls of this * method should ensure that they always publish the sequence afterward. E.g.

*
     * long sequence = ringBuffer.next();
     * try {
     *     Event e = ringBuffer.get(sequence);
     *     // Do some work with the event.
     * } finally {
     *     ringBuffer.publish(sequence);
     * }
     * 
*

This method will not block if there is not space available in the ring * buffer, instead it will throw an {@link InsufficientCapacityException}.

* * @return The next sequence to publish to. * @throws InsufficientCapacityException if the necessary space in the ring buffer is not available * @see com.lmax.disruptor.RingBuffer#publish(long) * @see com.lmax.disruptor.RingBuffer#get(long) */ @Override public long tryNext() throws InsufficientCapacityException { return sequencer.tryNext(); } /** * The same functionality as {@link com.lmax.disruptor.RingBuffer#tryNext()}, but allows the caller to attempt * to claim the next n sequences. * * @param n number of slots to claim * @return sequence number of the highest slot claimed * @throws InsufficientCapacityException if the necessary space in the ring buffer is not available */ @Override public long tryNext(final int n) throws InsufficientCapacityException { return sequencer.tryNext(n); } /** * Resets the cursor to a specific value. This can be applied at any time, but it is worth noting * that it can cause a data race and should only be used in controlled circumstances. E.g. during * initialisation. * * @param sequence The sequence to reset too. * @throws IllegalStateException If any gating sequences have already been specified. */ @Deprecated public void resetTo(final long sequence) { sequencer.claim(sequence); sequencer.publish(sequence); } /** * Sets the cursor to a specific sequence and returns the preallocated entry that is stored there. This * can cause a data race and should only be done in controlled circumstances, e.g. during initialisation. * * @param sequence The sequence to claim. * @return The preallocated event. */ public E claimAndGetPreallocated(final long sequence) { sequencer.claim(sequence); return get(sequence); } /** * Determines if the event for a given sequence is currently available. * *

Note that this does not guarantee that event will still be available * on the next interaction with the RingBuffer. For example, it is not * necessarily safe to write code like this: * *

{@code
     * if (ringBuffer.isAvailable(sequence))
     * {
     *     final E e = ringBuffer.get(sequence);
     *     // ...do something with e
     * }
     * }
* *

because there is a race between the reading thread and the writing thread. * *

This method will also return false when querying for sequences that are * behind the ring buffer's wrap point. * * @param sequence The sequence to identify the entry. * @return If the event published with the given sequence number is currently available. */ public boolean isAvailable(final long sequence) { return sequencer.isAvailable(sequence); } /** * Add the specified gating sequences to this instance of the Disruptor. They will * safely and atomically added to the list of gating sequences. * * @param gatingSequences The sequences to add. */ public void addGatingSequences(final Sequence... gatingSequences) { sequencer.addGatingSequences(gatingSequences); } /** * Get the minimum sequence value from all of the gating sequences * added to this ringBuffer. * * @return The minimum gating sequence or the cursor sequence if * no sequences have been added. */ public long getMinimumGatingSequence() { return sequencer.getMinimumSequence(); } /** * Remove the specified sequence from this ringBuffer. * * @param sequence to be removed. * @return true if this sequence was found, false otherwise. */ public boolean removeGatingSequence(final Sequence sequence) { return sequencer.removeGatingSequence(sequence); } /** * Create a new SequenceBarrier to be used by an EventProcessor to track which messages * are available to be read from the ring buffer given a list of sequences to track. * * @param sequencesToTrack the additional sequences to track * @return A sequence barrier that will track the specified sequences. * @see SequenceBarrier */ public SequenceBarrier newBarrier(final Sequence... sequencesToTrack) { return sequencer.newBarrier(sequencesToTrack); } /** * Creates an event poller for this ring buffer gated on the supplied sequences. * * @param gatingSequences to be gated on. * @return A poller that will gate on this ring buffer and the supplied sequences. */ public EventPoller newPoller(final Sequence... gatingSequences) { return sequencer.newPoller(this, gatingSequences); } /** * Get the current cursor value for the ring buffer. The actual value received * will depend on the type of {@link Sequencer} that is being used. * * @see MultiProducerSequencer * @see SingleProducerSequencer */ @Override public long getCursor() { return sequencer.getCursor(); } /** * The size of the buffer. * * @return size of buffer */ @Override public int getBufferSize() { return bufferSize; } /** * Given specified requiredCapacity determines if that amount of space * is available. Note, you can not assume that if this method returns true * that a call to {@link com.lmax.disruptor.RingBuffer#next()} will not block. Especially true if this * ring buffer is set up to handle multiple producers. * * @param requiredCapacity The capacity to check for. * @return true If the specified requiredCapacity is available * false if not. */ @Override public boolean hasAvailableCapacity(final int requiredCapacity) { return sequencer.hasAvailableCapacity(requiredCapacity); } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslator) */ @Override public void publishEvent(final EventTranslator translator) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslator) */ @Override public boolean tryPublishEvent(final EventTranslator translator) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorOneArg, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorOneArg, A) */ @Override public void publishEvent(final EventTranslatorOneArg translator, final A arg0) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorOneArg, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorOneArg, A) */ @Override public boolean tryPublishEvent(final EventTranslatorOneArg translator, final A arg0) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorTwoArg, Object, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorTwoArg, A, B) */ @Override public void publishEvent(final EventTranslatorTwoArg translator, final A arg0, final B arg1) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0, arg1); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorTwoArg, Object, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorTwoArg, A, B) */ @Override public boolean tryPublishEvent(final EventTranslatorTwoArg translator, final A arg0, final B arg1) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0, arg1); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorThreeArg, Object, Object, Object) * com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorThreeArg, A, B, C) */ @Override public void publishEvent(final EventTranslatorThreeArg translator, final A arg0, final B arg1, final C arg2) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, arg0, arg1, arg2); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorThreeArg, Object, Object, Object) * com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorThreeArg, A, B, C) */ @Override public boolean tryPublishEvent(final EventTranslatorThreeArg translator, final A arg0, final B arg1, final C arg2) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, arg0, arg1, arg2); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvent(com.lmax.disruptor.EventTranslatorVararg, java.lang.Object...) */ @Override public void publishEvent(final EventTranslatorVararg translator, final Object... args) { final long sequence = sequencer.next(); translateAndPublish(translator, sequence, args); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvent(com.lmax.disruptor.EventTranslatorVararg, java.lang.Object...) */ @Override public boolean tryPublishEvent(final EventTranslatorVararg translator, final Object... args) { try { final long sequence = sequencer.tryNext(); translateAndPublish(translator, sequence, args); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslator[]) */ @Override public void publishEvents(final EventTranslator[] translators) { publishEvents(translators, 0, translators.length); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslator[], int, int) */ @Override public void publishEvents(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBounds(translators, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translators, batchStartsAt, batchSize, finalSequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslator[]) */ @Override public boolean tryPublishEvents(final EventTranslator[] translators) { return tryPublishEvents(translators, 0, translators.length); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslator[], int, int) */ @Override public boolean tryPublishEvents(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBounds(translators, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translators, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, A[]) */ @Override public void publishEvents(final EventTranslatorOneArg translator, final A[] arg0) { publishEvents(translator, 0, arg0.length, arg0); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, A[]) */ @Override public void publishEvents(final EventTranslatorOneArg translator, final int batchStartsAt, final int batchSize, final A[] arg0) { checkBounds(arg0, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, batchStartsAt, batchSize, finalSequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, A[]) */ @Override public boolean tryPublishEvents(final EventTranslatorOneArg translator, final A[] arg0) { return tryPublishEvents(translator, 0, arg0.length, arg0); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorOneArg, int, int, A[]) */ @Override public boolean tryPublishEvents( final EventTranslatorOneArg translator, final int batchStartsAt, final int batchSize, final A[] arg0) { checkBounds(arg0, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, A[], B[]) */ @Override public void publishEvents(final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1) { publishEvents(translator, 0, arg0.length, arg0, arg1); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, A[], B[]) */ @Override public void publishEvents( final EventTranslatorTwoArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1) { checkBounds(arg0, arg1, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, arg1, batchStartsAt, batchSize, finalSequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, A[], B[]) */ @Override public boolean tryPublishEvents(final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1) { return tryPublishEvents(translator, 0, arg0.length, arg0, arg1); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorTwoArg, int, int, A[], B[]) */ @Override public boolean tryPublishEvents( final EventTranslatorTwoArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1) { checkBounds(arg0, arg1, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, arg1, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, A[], B[], C[]) */ @Override public void publishEvents(final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2) { publishEvents(translator, 0, arg0.length, arg0, arg1, arg2); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, A[], B[], C[]) */ @Override public void publishEvents( final EventTranslatorThreeArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1, final C[] arg2) { checkBounds(arg0, arg1, arg2, batchStartsAt, batchSize); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, arg0, arg1, arg2, batchStartsAt, batchSize, finalSequence); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, A[], B[], C[]) */ @Override public boolean tryPublishEvents( final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2) { return tryPublishEvents(translator, 0, arg0.length, arg0, arg1, arg2); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, Object[], Object[], Object[]) * com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorThreeArg, int, int, A[], B[], C[]) */ @Override public boolean tryPublishEvents( final EventTranslatorThreeArg translator, final int batchStartsAt, final int batchSize, final A[] arg0, final B[] arg1, final C[] arg2) { checkBounds(arg0, arg1, arg2, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, arg0, arg1, arg2, batchStartsAt, batchSize, finalSequence); return true; } catch (InsufficientCapacityException e) { return false; } } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorVararg, java.lang.Object[][]) */ @Override public void publishEvents(final EventTranslatorVararg translator, final Object[]... args) { publishEvents(translator, 0, args.length, args); } /** * @see com.lmax.disruptor.EventSink#publishEvents(com.lmax.disruptor.EventTranslatorVararg, int, int, java.lang.Object[][]) */ @Override public void publishEvents(final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final Object[]... args) { checkBounds(batchStartsAt, batchSize, args); final long finalSequence = sequencer.next(batchSize); translateAndPublishBatch(translator, batchStartsAt, batchSize, finalSequence, args); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorVararg, java.lang.Object[][]) */ @Override public boolean tryPublishEvents(final EventTranslatorVararg translator, final Object[]... args) { return tryPublishEvents(translator, 0, args.length, args); } /** * @see com.lmax.disruptor.EventSink#tryPublishEvents(com.lmax.disruptor.EventTranslatorVararg, int, int, java.lang.Object[][]) */ @Override public boolean tryPublishEvents( final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final Object[]... args) { checkBounds(args, batchStartsAt, batchSize); try { final long finalSequence = sequencer.tryNext(batchSize); translateAndPublishBatch(translator, batchStartsAt, batchSize, finalSequence, args); return true; } catch (InsufficientCapacityException e) { return false; } } /** * Publish the specified sequence. This action marks this particular * message as being available to be read. * * @param sequence the sequence to publish. */ @Override public void publish(final long sequence) { sequencer.publish(sequence); } /** * Publish the specified sequences. This action marks these particular * messages as being available to be read. * * @param lo the lowest sequence number to be published * @param hi the highest sequence number to be published * @see Sequencer#next(int) */ @Override public void publish(final long lo, final long hi) { sequencer.publish(lo, hi); } /** * Get the remaining capacity for this ringBuffer. * * @return The number of slots remaining. */ @Override public long remainingCapacity() { return sequencer.remainingCapacity(); } private void checkBounds(final EventTranslator[] translators, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(translators, batchStartsAt, batchSize); } private void checkBatchSizing(final int batchStartsAt, final int batchSize) { if (batchStartsAt < 0 || batchSize < 0) { throw new IllegalArgumentException("Both batchStartsAt and batchSize must be positive but got: batchStartsAt " + batchStartsAt + " and batchSize " + batchSize); } else if (batchSize > bufferSize) { throw new IllegalArgumentException("The ring buffer cannot accommodate " + batchSize + " it only has space for " + bufferSize + " entities."); } } private void checkBounds(final A[] arg0, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); } private void checkBounds(final A[] arg0, final B[] arg1, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); batchOverRuns(arg1, batchStartsAt, batchSize); } private void checkBounds( final A[] arg0, final B[] arg1, final C[] arg2, final int batchStartsAt, final int batchSize) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(arg0, batchStartsAt, batchSize); batchOverRuns(arg1, batchStartsAt, batchSize); batchOverRuns(arg2, batchStartsAt, batchSize); } private void checkBounds(final int batchStartsAt, final int batchSize, final Object[][] args) { checkBatchSizing(batchStartsAt, batchSize); batchOverRuns(args, batchStartsAt, batchSize); } private void batchOverRuns(final A[] arg0, final int batchStartsAt, final int batchSize) { if (batchStartsAt + batchSize > arg0.length) { throw new IllegalArgumentException( "A batchSize of: " + batchSize + " with batchStatsAt of: " + batchStartsAt + " will overrun the available number of arguments: " + (arg0.length - batchStartsAt)); } } private void translateAndPublish(final EventTranslator translator, final long sequence) { try { translator.translateTo(get(sequence), sequence); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorOneArg translator, final long sequence, final A arg0) { try { translator.translateTo(get(sequence), sequence, arg0); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorTwoArg translator, final long sequence, final A arg0, final B arg1) { try { translator.translateTo(get(sequence), sequence, arg0, arg1); } finally { sequencer.publish(sequence); } } private void translateAndPublish( final EventTranslatorThreeArg translator, final long sequence, final A arg0, final B arg1, final C arg2) { try { translator.translateTo(get(sequence), sequence, arg0, arg1, arg2); } finally { sequencer.publish(sequence); } } private void translateAndPublish(final EventTranslatorVararg translator, final long sequence, final Object... args) { try { translator.translateTo(get(sequence), sequence, args); } finally { sequencer.publish(sequence); } } private void translateAndPublishBatch( final EventTranslator[] translators, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { final EventTranslator translator = translators[i]; translator.translateTo(get(sequence), sequence++); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorOneArg translator, final A[] arg0, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorTwoArg translator, final A[] arg0, final B[] arg1, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i], arg1[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorThreeArg translator, final A[] arg0, final B[] arg1, final C[] arg2, final int batchStartsAt, final int batchSize, final long finalSequence) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, arg0[i], arg1[i], arg2[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } private void translateAndPublishBatch( final EventTranslatorVararg translator, final int batchStartsAt, final int batchSize, final long finalSequence, final Object[][] args) { final long initialSequence = finalSequence - (batchSize - 1); try { long sequence = initialSequence; final int batchEndsAt = batchStartsAt + batchSize; for (int i = batchStartsAt; i < batchEndsAt; i++) { translator.translateTo(get(sequence), sequence++, args[i]); } } finally { sequencer.publish(initialSequence, finalSequence); } } @Override public String toString() { return "RingBuffer{" + "bufferSize=" + bufferSize + ", sequencer=" + sequencer + "}"; } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/SequenceDoublePadded.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.alternatives; import com.lmax.disruptor.util.UnsafeAccess; import sun.misc.Unsafe; // https://github.com/LMAX-Exchange/disruptor/issues/231 class LhsPaddingDouble { protected long p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15; } class ValueDoublePadded extends LhsPaddingDouble { protected volatile long value; } class RhsPaddingDouble extends ValueDoublePadded { protected long p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15; } /** * Concurrent sequence class used for tracking the progress of * the ring buffer and event processors. Support a number * of concurrent operations including CAS and order writes. * *

Also attempts to be more efficient with regards to false * sharing by adding padding around the volatile field. */ public class SequenceDoublePadded extends RhsPaddingDouble { static final long INITIAL_VALUE = -1L; private static final Unsafe UNSAFE; private static final long VALUE_OFFSET; static { UNSAFE = UnsafeAccess.getUnsafe(); try { VALUE_OFFSET = UNSAFE.objectFieldOffset(ValueDoublePadded.class.getDeclaredField("value")); } catch (final Exception e) { throw new RuntimeException(e); } } /** * Create a sequence initialised to -1. */ public SequenceDoublePadded() { this(INITIAL_VALUE); } /** * Create a sequence with a specified initial value. * * @param initialValue The initial value for this sequence. */ public SequenceDoublePadded(final long initialValue) { UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue); } /** * Perform a volatile read of this sequence's value. * * @return The current value of the sequence. */ public long get() { return value; } /** * Perform an ordered write of this sequence. The intent is * a Store/Store barrier between this write and any previous * store. * * @param value The new value for the sequence. */ public void set(final long value) { UNSAFE.putOrderedLong(this, VALUE_OFFSET, value); } /** * Performs a volatile write of this sequence. The intent is * a Store/Store barrier between this write and any previous * write and a Store/Load barrier between this write and any * subsequent volatile read. * * @param value The new value for the sequence. */ public void setVolatile(final long value) { UNSAFE.putLongVolatile(this, VALUE_OFFSET, value); } /** * Perform a compare and set operation on the sequence. * * @param expectedValue The expected current value. * @param newValue The value to update to. * @return true if the operation succeeds, false otherwise. */ public boolean compareAndSet(final long expectedValue, final long newValue) { return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue); } /** * Atomically increment the sequence by one. * * @return The value after the increment */ public long incrementAndGet() { return addAndGet(1L); } /** * Atomically add the supplied value. * * @param increment The value to add to the sequence. * @return The value after the increment. */ public long addAndGet(final long increment) { long currentValue; long newValue; do { currentValue = get(); newValue = currentValue + increment; } while (!compareAndSet(currentValue, newValue)); return newValue; } @Override public String toString() { return Long.toString(get()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/SequenceUnsafe.java ================================================ package com.lmax.disruptor.alternatives; import com.lmax.disruptor.util.UnsafeAccess; import sun.misc.Unsafe; class LhsPaddingUnsafe { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; } class ValueUnsafe extends LhsPaddingUnsafe { protected volatile long value; } class RhsPaddingUnsafe extends ValueUnsafe { protected byte p90, p91, p92, p93, p94, p95, p96, p97, p100, p101, p102, p103, p104, p105, p106, p107, p110, p111, p112, p113, p114, p115, p116, p117, p120, p121, p122, p123, p124, p125, p126, p127, p130, p131, p132, p133, p134, p135, p136, p137, p140, p141, p142, p143, p144, p145, p146, p147, p150, p151, p152, p153, p154, p155, p156, p157; } /** * Concurrent sequence class used for tracking the progress of * the ring buffer and event processors. Support a number * of concurrent operations including CAS and order writes. * *

Also attempts to be more efficient with regards to false * sharing by adding padding around the volatile field. */ public class SequenceUnsafe extends RhsPaddingUnsafe { static final long INITIAL_VALUE = -1L; private static final Unsafe UNSAFE; private static final long VALUE_OFFSET; static { UNSAFE = UnsafeAccess.getUnsafe(); try { VALUE_OFFSET = UNSAFE.objectFieldOffset(ValueUnsafe.class.getDeclaredField("value")); } catch (final Exception e) { throw new RuntimeException(e); } } /** * Create a sequence initialised to -1. */ public SequenceUnsafe() { this(INITIAL_VALUE); } /** * Create a sequence with a specified initial value. * * @param initialValue The initial value for this sequence. */ public SequenceUnsafe(final long initialValue) { UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue); } /** * Perform a volatile read of this sequence's value. * * @return The current value of the sequence. */ public long get() { return value; } /** * Perform an ordered write of this sequence. The intent is * a Store/Store barrier between this write and any previous * store. * * @param value The new value for the sequence. */ public void set(final long value) { UNSAFE.putOrderedLong(this, VALUE_OFFSET, value); } /** * Performs a volatile write of this sequence. The intent is * a Store/Store barrier between this write and any previous * write and a Store/Load barrier between this write and any * subsequent volatile read. * * @param value The new value for the sequence. */ public void setVolatile(final long value) { UNSAFE.putLongVolatile(this, VALUE_OFFSET, value); } /** * Perform a compare and set operation on the sequence. * * @param expectedValue The expected current value. * @param newValue The value to update to. * @return true if the operation succeeds, false otherwise. */ public boolean compareAndSet(final long expectedValue, final long newValue) { return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue); } /** * Atomically increment the sequence by one. * * @return The value after the increment */ public long incrementAndGet() { return addAndGet(1L); } /** * Atomically add the supplied value. * * @param increment The value to add to the sequence. * @return The value after the increment. */ public long addAndGet(final long increment) { long currentValue; long newValue; do { currentValue = get(); newValue = currentValue + increment; } while (!compareAndSet(currentValue, newValue)); return newValue; } @Override public String toString() { return Long.toString(get()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/SequenceVarHandle.java ================================================ package com.lmax.disruptor.alternatives; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; class LhsPaddingVarHandle { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; } class ValueVarHandle extends LhsPaddingVarHandle { protected volatile long value; } class RhsPaddingVarHandle extends ValueVarHandle { protected byte p90, p91, p92, p93, p94, p95, p96, p97, p100, p101, p102, p103, p104, p105, p106, p107, p110, p111, p112, p113, p114, p115, p116, p117, p120, p121, p122, p123, p124, p125, p126, p127, p130, p131, p132, p133, p134, p135, p136, p137, p140, p141, p142, p143, p144, p145, p146, p147, p150, p151, p152, p153, p154, p155, p156, p157; } /** * Concurrent sequence class used for tracking the progress of * the ring buffer and event processors. Support a number * of concurrent operations including CAS and order writes. * *

Also attempts to be more efficient with regards to false * sharing by adding padding around the volatile field. */ public class SequenceVarHandle extends RhsPaddingVarHandle { static final long INITIAL_VALUE = -1L; private static final VarHandle VALUE_FIELD; static { try { VALUE_FIELD = MethodHandles.lookup().in(SequenceVarHandle.class) .findVarHandle(SequenceVarHandle.class, "value", long.class); } catch (final Exception e) { throw new RuntimeException(e); } } /** * Create a sequence initialised to -1. */ public SequenceVarHandle() { this(INITIAL_VALUE); } /** * Create a sequence with a specified initial value. * * @param initialValue The initial value for this sequence. */ public SequenceVarHandle(final long initialValue) { VALUE_FIELD.setRelease(this, initialValue); } /** * Perform a volatile read of this sequence's value. * * @return The current value of the sequence. */ public long get() { return (long) VALUE_FIELD.getAcquire(this); } /** * Perform an ordered write of this sequence. The intent is * a Store/Store barrier between this write and any previous * store. * * @param value The new value for the sequence. */ public void set(final long value) { VALUE_FIELD.setRelease(this, value); } /** * Performs a volatile write of this sequence. The intent is * a Store/Store barrier between this write and any previous * write and a Store/Load barrier between this write and any * subsequent volatile read. * * @param value The new value for the sequence. */ public void setVolatile(final long value) { VALUE_FIELD.setVolatile(this, value); } /** * Perform a compare and set operation on the sequence. * * @param expectedValue The expected current value. * @param newValue The value to update to. * @return true if the operation succeeds, false otherwise. */ public boolean compareAndSet(final long expectedValue, final long newValue) { return VALUE_FIELD.compareAndSet(this, expectedValue, newValue); } /** * Atomically increment the sequence by one. * * @return The value after the increment */ public long incrementAndGet() { return addAndGet(1L); } /** * Atomically add the supplied value. * * @param increment The value to add to the sequence. * @return The value after the increment. */ public long addAndGet(final long increment) { return (long) VALUE_FIELD.getAndAdd(this, increment) + increment; } @Override public String toString() { return Long.toString(get()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/SequenceVarHandleArray.java ================================================ package com.lmax.disruptor.alternatives; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; /** * Concurrent sequence class used for tracking the progress of * the ring buffer and event processors. Support a number * of concurrent operations including CAS and order writes. * *

Also attempts to be more efficient with regards to false * sharing by adding padding around the volatile field. */ public class SequenceVarHandleArray { private static final int VALUE_INDEX = 8; private static final VarHandle VALUE_FIELD = MethodHandles.arrayElementVarHandle(long[].class); private final long[] paddedValue = new long[16]; static final long INITIAL_VALUE = -1L; /** * Create a sequence initialised to -1. */ public SequenceVarHandleArray() { this(INITIAL_VALUE); } /** * Create a sequence with a specified initial value. * * @param initialValue The initial value for this sequence. */ public SequenceVarHandleArray(final long initialValue) { this.set(initialValue); } /** * Perform a volatile read of this sequence's value. * * @return The current value of the sequence. */ public long get() { return (long) VALUE_FIELD.getAcquire(this.paddedValue, VALUE_INDEX); } /** * Perform an ordered write of this sequence. The intent is * a Store/Store barrier between this write and any previous * store. * * @param value The new value for the sequence. */ public void set(final long value) { VALUE_FIELD.setRelease(this.paddedValue, VALUE_INDEX, value); } /** * Performs a volatile write of this sequence. The intent is * a Store/Store barrier between this write and any previous * write and a Store/Load barrier between this write and any * subsequent volatile read. * * @param value The new value for the sequence. */ public void setVolatile(final long value) { VALUE_FIELD.setVolatile(this.paddedValue, VALUE_INDEX, value); } /** * Perform a compare and set operation on the sequence. * * @param expectedValue The expected current value. * @param newValue The value to update to. * @return true if the operation succeeds, false otherwise. */ public boolean compareAndSet(final long expectedValue, final long newValue) { return VALUE_FIELD.compareAndSet(this.paddedValue, VALUE_INDEX, expectedValue, newValue); } /** * Atomically increment the sequence by one. * * @return The value after the increment */ public long incrementAndGet() { return addAndGet(1L); } /** * Atomically add the supplied value. * * @param increment The value to add to the sequence. * @return The value after the increment. */ public long addAndGet(final long increment) { return (long) VALUE_FIELD.getAndAdd(this.paddedValue, VALUE_INDEX, increment) + increment; } @Override public String toString() { return Long.toString(get()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/alternatives/SequenceVarHandleBarrier.java ================================================ package com.lmax.disruptor.alternatives; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; class LhsPaddingVarHandleBarrier { protected byte p10, p11, p12, p13, p14, p15, p16, p17, p20, p21, p22, p23, p24, p25, p26, p27, p30, p31, p32, p33, p34, p35, p36, p37, p40, p41, p42, p43, p44, p45, p46, p47, p50, p51, p52, p53, p54, p55, p56, p57, p60, p61, p62, p63, p64, p65, p66, p67, p70, p71, p72, p73, p74, p75, p76, p77; } class ValueVarHandleBarrier extends LhsPaddingVarHandleBarrier { protected long value; } class RhsPaddingVarHandleBarrier extends ValueVarHandleBarrier { protected byte p90, p91, p92, p93, p94, p95, p96, p97, p100, p101, p102, p103, p104, p105, p106, p107, p110, p111, p112, p113, p114, p115, p116, p117, p120, p121, p122, p123, p124, p125, p126, p127, p130, p131, p132, p133, p134, p135, p136, p137, p140, p141, p142, p143, p144, p145, p146, p147, p150, p151, p152, p153, p154, p155, p156, p157; } /** * Concurrent sequence class used for tracking the progress of * the ring buffer and event processors. Support a number * of concurrent operations including CAS and order writes. * *

Also attempts to be more efficient with regards to false * sharing by adding padding around the volatile field. */ public class SequenceVarHandleBarrier extends RhsPaddingVarHandleBarrier { static final long INITIAL_VALUE = -1L; private static final VarHandle VALUE_FIELD; static { try { VALUE_FIELD = MethodHandles.lookup().in(SequenceVarHandleBarrier.class) .findVarHandle(SequenceVarHandleBarrier.class, "value", long.class); } catch (final Exception e) { throw new RuntimeException(e); } } /** * Create a sequence initialised to -1. */ public SequenceVarHandleBarrier() { this(INITIAL_VALUE); } /** * Create a sequence with a specified initial value. * * @param initialValue The initial value for this sequence. */ public SequenceVarHandleBarrier(final long initialValue) { VarHandle.releaseFence(); this.value = initialValue; } /** * Perform a volatile read of this sequence's value. * * @return The current value of the sequence. */ public long get() { long value = this.value; VarHandle.acquireFence(); return value; } /** * Perform an ordered write of this sequence. The intent is * a Store/Store barrier between this write and any previous * store. * * @param value The new value for the sequence. */ public void set(final long value) { VarHandle.releaseFence(); this.value = value; } /** * Performs a volatile write of this sequence. The intent is * a Store/Store barrier between this write and any previous * write and a Store/Load barrier between this write and any * subsequent volatile read. * * @param value The new value for the sequence. */ public void setVolatile(final long value) { VarHandle.releaseFence(); this.value = value; VarHandle.fullFence(); } /** * Perform a compare and set operation on the sequence. * * @param expectedValue The expected current value. * @param newValue The value to update to. * @return true if the operation succeeds, false otherwise. */ public boolean compareAndSet(final long expectedValue, final long newValue) { return VALUE_FIELD.compareAndSet(this, expectedValue, newValue); } /** * Atomically increment the sequence by one. * * @return The value after the increment */ public long incrementAndGet() { return addAndGet(1L); } /** * Atomically add the supplied value. * * @param increment The value to add to the sequence. * @return The value after the increment. */ public long addAndGet(final long increment) { return (long) VALUE_FIELD.getAndAdd(this, increment) + increment; } @Override public String toString() { return Long.toString(get()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/ConsumerRepositoryTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.dsl.stubs.SleepingEventHandler; import com.lmax.disruptor.support.DummyEventProcessor; import com.lmax.disruptor.support.DummySequenceBarrier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; public class ConsumerRepositoryTest { private ConsumerRepository consumerRepository; private EventProcessor eventProcessor1; private EventProcessor eventProcessor2; private SleepingEventHandler handler1; private SleepingEventHandler handler2; private SequenceBarrier barrier1; private SequenceBarrier barrier2; @BeforeEach public void setUp() { consumerRepository = new ConsumerRepository(); eventProcessor1 = new DummyEventProcessor(new Sequence()); eventProcessor2 = new DummyEventProcessor(new Sequence()); eventProcessor1.run(); eventProcessor2.run(); handler1 = new SleepingEventHandler(); handler2 = new SleepingEventHandler(); barrier1 = new DummySequenceBarrier(); barrier2 = new DummySequenceBarrier(); } @Test public void shouldGetBarrierByHandler() { consumerRepository.add(eventProcessor1, handler1, barrier1); assertThat(consumerRepository.getBarrierFor(handler1), sameInstance(barrier1)); } @Test public void shouldReturnNullForBarrierWhenHandlerIsNotRegistered() { assertThat(consumerRepository.getBarrierFor(handler1), is(nullValue())); } @Test public void shouldRetrieveEventProcessorForHandler() { consumerRepository.add(eventProcessor1, handler1, barrier1); assertThat(consumerRepository.getEventProcessorFor(handler1), sameInstance(eventProcessor1)); } @Test public void shouldThrowExceptionWhenHandlerIsNotRegistered() { assertThrows(IllegalArgumentException.class, () -> consumerRepository.getEventProcessorFor(new SleepingEventHandler()) ); } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/DisruptorTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl; import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BatchEventProcessorBuilder; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.ExceptionHandler; import com.lmax.disruptor.FatalExceptionHandler; import com.lmax.disruptor.RewindableEventHandler; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.SimpleBatchRewindStrategy; import com.lmax.disruptor.TimeoutException; import com.lmax.disruptor.dsl.stubs.DelayedEventHandler; import com.lmax.disruptor.dsl.stubs.EventHandlerStub; import com.lmax.disruptor.dsl.stubs.EvilEqualsEventHandler; import com.lmax.disruptor.dsl.stubs.ExceptionThrowingEventHandler; import com.lmax.disruptor.dsl.stubs.SleepingEventHandler; import com.lmax.disruptor.dsl.stubs.StubExceptionHandler; import com.lmax.disruptor.dsl.stubs.StubPublisher; import com.lmax.disruptor.dsl.stubs.StubThreadFactory; import com.lmax.disruptor.support.DummyEventHandler; import com.lmax.disruptor.support.TestEvent; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; public class DisruptorTest { private static final int TIMEOUT_IN_SECONDS = 2; public final StubThreadFactory executor = new StubThreadFactory(); private final Collection delayedEventHandlers = new ArrayList<>(); private Disruptor disruptor; private RingBuffer ringBuffer; @BeforeEach public void setUp() { createDisruptor(); } @AfterEach public void tearDown() { for (DelayedEventHandler delayedEventHandler : delayedEventHandlers) { delayedEventHandler.stopWaiting(); } disruptor.halt(); executor.joinAllThreads(); } @Test public void shouldHaveStartedAfterStartCalled() { assertFalse(disruptor.hasStarted(), "Should only be set to started after start is called"); disruptor.start(); assertTrue(disruptor.hasStarted(), "Should be set to started after start is called"); } @Test public void shouldProcessMessagesPublishedBeforeStartIsCalled() throws Exception { final CountDownLatch eventCounter = new CountDownLatch(2); disruptor.handleEventsWith((event, sequence, endOfBatch) -> eventCounter.countDown()); disruptor.publishEvent((event, sequence) -> { }); disruptor.start(); disruptor.publishEvent((event, sequence) -> { }); if (!eventCounter.await(5, TimeUnit.SECONDS)) { fail("Did not process event published before start was called. Missed events: " + eventCounter.getCount()); } } @Test public void shouldBatchOfEvents() throws Exception { final CountDownLatch eventCounter = new CountDownLatch(2); disruptor.handleEventsWith((event, sequence, endOfBatch) -> eventCounter.countDown()); disruptor.start(); disruptor.publishEvents((event, sequence, arg) -> { }, new Object[] { "a", "b" }); if (!eventCounter.await(5, TimeUnit.SECONDS)) { fail("Did not process event published before start was called. Missed events: " + eventCounter.getCount()); } } @Test public void shouldHandleEventsWithRewindableEventHandlers() throws Exception { final CountDownLatch eventCounter = new CountDownLatch(2); final RewindableEventHandler testEventRewindableEventHandler = (event, sequence, endOfBatch) -> eventCounter.countDown(); disruptor.handleEventsWith(new SimpleBatchRewindStrategy(), testEventRewindableEventHandler); disruptor.start(); disruptor.publishEvents((event, sequence, arg) -> { }, new Object[] { "a", "b" }); if (!eventCounter.await(5, TimeUnit.SECONDS)) { fail("Did not process event published before start was called. Missed events: " + eventCounter.getCount()); } } @Test public void shouldAddEventProcessorsAfterPublishing() { RingBuffer rb = disruptor.getRingBuffer(); BatchEventProcessor b1 = new BatchEventProcessorBuilder().build( rb, rb.newBarrier(), new SleepingEventHandler()); BatchEventProcessor b2 = new BatchEventProcessorBuilder().build( rb, rb.newBarrier(b1.getSequence()), new SleepingEventHandler()); BatchEventProcessor b3 = new BatchEventProcessorBuilder().build( rb, rb.newBarrier(b2.getSequence()), new SleepingEventHandler()); assertThat(b1.getSequence().get(), is(-1L)); assertThat(b2.getSequence().get(), is(-1L)); assertThat(b3.getSequence().get(), is(-1L)); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); disruptor.handleEventsWith(b1, b2, b3); assertThat(b1.getSequence().get(), is(5L)); assertThat(b2.getSequence().get(), is(5L)); assertThat(b3.getSequence().get(), is(5L)); } @Test public void shouldSetSequenceForHandlerIfAddedAfterPublish() { RingBuffer rb = disruptor.getRingBuffer(); EventHandler b1 = new SleepingEventHandler(); EventHandler b2 = new SleepingEventHandler(); EventHandler b3 = new SleepingEventHandler(); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); disruptor.handleEventsWith(b1, b2, b3); assertThat(disruptor.getSequenceValueFor(b1), is(5L)); assertThat(disruptor.getSequenceValueFor(b2), is(5L)); assertThat(disruptor.getSequenceValueFor(b3), is(5L)); } @Test public void shouldGetSequenceBarrierForHandler() { RingBuffer rb = disruptor.getRingBuffer(); EventHandler handler = new DummyEventHandler<>(); disruptor.handleEventsWith(handler); disruptor.start(); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); assertThat(disruptor.getBarrierFor(handler).getCursor(), is(5L)); } @Test public void shouldGetSequenceBarrierForHandlerIfAddedAfterPublish() { RingBuffer rb = disruptor.getRingBuffer(); EventHandler handler = new DummyEventHandler<>(); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); rb.publish(rb.next()); disruptor.handleEventsWith(handler); disruptor.start(); assertThat(disruptor.getBarrierFor(handler).getCursor(), is(5L)); } @Test public void shouldCreateEventProcessorGroupForFirstEventProcessors() { executor.ignoreExecutions(); final EventHandler eventHandler1 = new SleepingEventHandler(); EventHandler eventHandler2 = new SleepingEventHandler(); final EventHandlerGroup eventHandlerGroup = disruptor.handleEventsWith(eventHandler1, eventHandler2); disruptor.start(); assertNotNull(eventHandlerGroup); assertThat(executor.getExecutionCount(), equalTo(2)); } @Test public void shouldMakeEntriesAvailableToFirstHandlersImmediately() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(2); EventHandler eventHandler = new EventHandlerStub<>(countDownLatch); disruptor.handleEventsWith(createDelayedEventHandler(), eventHandler); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch); } @Test public void shouldWaitUntilAllFirstEventProcessorsProcessEventBeforeMakingItAvailableToDependentEventProcessors() throws Exception { DelayedEventHandler eventHandler1 = createDelayedEventHandler(); CountDownLatch countDownLatch = new CountDownLatch(2); EventHandler eventHandler2 = new EventHandlerStub<>(countDownLatch); disruptor.handleEventsWith(eventHandler1).then(eventHandler2); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch, eventHandler1); } @Test public void shouldSupportAddingCustomEventProcessorWithFactory() { RingBuffer rb = disruptor.getRingBuffer(); BatchEventProcessor b1 = new BatchEventProcessorBuilder().build( rb, rb.newBarrier(), new SleepingEventHandler()); EventProcessorFactory b2 = (ringBuffer, barrierSequences) -> new BatchEventProcessorBuilder().build( ringBuffer, ringBuffer.newBarrier(barrierSequences), new SleepingEventHandler()); disruptor.handleEventsWith(b1).then(b2); disruptor.start(); assertThat(executor.getExecutionCount(), equalTo(2)); } @Test public void shouldAllowSpecifyingSpecificEventProcessorsToWaitFor() throws Exception { DelayedEventHandler handler1 = createDelayedEventHandler(); DelayedEventHandler handler2 = createDelayedEventHandler(); CountDownLatch countDownLatch = new CountDownLatch(2); EventHandler handlerWithBarrier = new EventHandlerStub<>(countDownLatch); disruptor.handleEventsWith(handler1, handler2); disruptor.after(handler1, handler2).handleEventsWith(handlerWithBarrier); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch, handler1, handler2); assertThat(executor.getExecutionCount(), equalTo(3)); } @Test public void shouldWaitOnAllProducersJoinedByAnd() throws Exception { DelayedEventHandler handler1 = createDelayedEventHandler(); DelayedEventHandler handler2 = createDelayedEventHandler(); CountDownLatch countDownLatch = new CountDownLatch(2); EventHandler handlerWithBarrier = new EventHandlerStub<>(countDownLatch); disruptor.handleEventsWith(handler1); final EventHandlerGroup handler2Group = disruptor.handleEventsWith(handler2); disruptor.after(handler1).and(handler2Group).handleEventsWith(handlerWithBarrier); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch, handler1, handler2); assertThat(executor.getExecutionCount(), equalTo(3)); } @Test public void shouldThrowExceptionIfHandlerIsNotAlreadyConsuming() { assertThrows(IllegalArgumentException.class, () -> disruptor.after(createDelayedEventHandler()).handleEventsWith(createDelayedEventHandler())); } @Test public void shouldTrackEventHandlersByIdentityNotEquality() { assertThrows(IllegalArgumentException.class, () -> { EvilEqualsEventHandler handler1 = new EvilEqualsEventHandler(); EvilEqualsEventHandler handler2 = new EvilEqualsEventHandler(); disruptor.handleEventsWith(handler1); // handler2.equals(handler1) but it hasn't yet been registered so should throw exception. disruptor.after(handler2); }); } @SuppressWarnings("deprecation") @Test public void shouldSupportSpecifyingAExceptionHandlerForEventProcessors() throws Exception { AtomicReference eventHandled = new AtomicReference<>(); ExceptionHandler exceptionHandler = new StubExceptionHandler(eventHandled); RuntimeException testException = new RuntimeException(); ExceptionThrowingEventHandler handler = new ExceptionThrowingEventHandler(testException); disruptor.handleExceptionsWith(exceptionHandler); disruptor.handleEventsWith(handler); publishEvent(); final Throwable actualException = waitFor(eventHandled); assertSame(testException, actualException); } @SuppressWarnings("deprecation") @Test public void shouldOnlyApplyExceptionsHandlersSpecifiedViaHandleExceptionsWithOnNewEventProcessors() throws Exception { AtomicReference eventHandled = new AtomicReference<>(); ExceptionHandler exceptionHandler = new StubExceptionHandler(eventHandled); RuntimeException testException = new RuntimeException(); ExceptionThrowingEventHandler handler = new ExceptionThrowingEventHandler(testException); disruptor.handleExceptionsWith(exceptionHandler); disruptor.handleEventsWith(handler); disruptor.handleExceptionsWith(new FatalExceptionHandler()); publishEvent(); final Throwable actualException = waitFor(eventHandled); assertSame(testException, actualException); } @Test public void shouldSupportSpecifyingADefaultExceptionHandlerForEventProcessors() throws Exception { AtomicReference eventHandled = new AtomicReference<>(); ExceptionHandler exceptionHandler = new StubExceptionHandler(eventHandled); RuntimeException testException = new RuntimeException(); ExceptionThrowingEventHandler handler = new ExceptionThrowingEventHandler(testException); disruptor.setDefaultExceptionHandler(exceptionHandler); disruptor.handleEventsWith(handler); publishEvent(); final Throwable actualException = waitFor(eventHandled); assertSame(testException, actualException); } @Test public void shouldApplyDefaultExceptionHandlerToExistingEventProcessors() throws Exception { AtomicReference eventHandled = new AtomicReference<>(); ExceptionHandler exceptionHandler = new StubExceptionHandler(eventHandled); RuntimeException testException = new RuntimeException(); ExceptionThrowingEventHandler handler = new ExceptionThrowingEventHandler(testException); disruptor.handleEventsWith(handler); disruptor.setDefaultExceptionHandler(exceptionHandler); publishEvent(); final Throwable actualException = waitFor(eventHandled); assertSame(testException, actualException); } @Test public void shouldBlockProducerUntilAllEventProcessorsHaveAdvanced() throws Exception { final DelayedEventHandler delayedEventHandler = createDelayedEventHandler(); disruptor.handleEventsWith(delayedEventHandler); final RingBuffer ringBuffer = disruptor.start(); delayedEventHandler.awaitStart(); final StubPublisher stubPublisher = new StubPublisher(ringBuffer); try { executor.newThread(stubPublisher).start(); assertProducerReaches(stubPublisher, 4, true); delayedEventHandler.processEvent(); delayedEventHandler.processEvent(); delayedEventHandler.processEvent(); delayedEventHandler.processEvent(); delayedEventHandler.processEvent(); assertProducerReaches(stubPublisher, 5, false); } finally { stubPublisher.halt(); } } @Test public void shouldBeAbleToOverrideTheExceptionHandlerForAEventProcessor() throws Exception { final RuntimeException testException = new RuntimeException(); final ExceptionThrowingEventHandler eventHandler = new ExceptionThrowingEventHandler(testException); disruptor.handleEventsWith(eventHandler); AtomicReference reference = new AtomicReference<>(); StubExceptionHandler exceptionHandler = new StubExceptionHandler(reference); disruptor.handleExceptionsFor(eventHandler).with(exceptionHandler); publishEvent(); final Throwable actualException = waitFor(reference); assertSame(testException, actualException); } @Test public void shouldThrowExceptionWhenAddingEventProcessorsAfterTheProducerBarrierHasBeenCreated() { assertThrows(IllegalStateException.class, () -> { executor.ignoreExecutions(); disruptor.handleEventsWith(new SleepingEventHandler()); disruptor.start(); disruptor.handleEventsWith(new SleepingEventHandler()); }); } @Test public void shouldThrowExceptionIfStartIsCalledTwice() { assertThrows(IllegalStateException.class, () -> { executor.ignoreExecutions(); disruptor.handleEventsWith(new SleepingEventHandler()); disruptor.start(); disruptor.start(); }); } @Test public void shouldSupportCustomProcessorsAsDependencies() throws Exception { RingBuffer ringBuffer = disruptor.getRingBuffer(); final DelayedEventHandler delayedEventHandler = createDelayedEventHandler(); CountDownLatch countDownLatch = new CountDownLatch(2); EventHandler handlerWithBarrier = new EventHandlerStub<>(countDownLatch); final BatchEventProcessor processor = new BatchEventProcessorBuilder().build(ringBuffer, ringBuffer.newBarrier(), delayedEventHandler); disruptor.handleEventsWith(processor).then(handlerWithBarrier); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch, delayedEventHandler); assertThat(executor.getExecutionCount(), equalTo(2)); } @Test public void shouldSupportHandlersAsDependenciesToCustomProcessors() throws Exception { final DelayedEventHandler delayedEventHandler = createDelayedEventHandler(); disruptor.handleEventsWith(delayedEventHandler); RingBuffer ringBuffer = disruptor.getRingBuffer(); CountDownLatch countDownLatch = new CountDownLatch(2); EventHandler handlerWithBarrier = new EventHandlerStub<>(countDownLatch); final SequenceBarrier sequenceBarrier = disruptor.after(delayedEventHandler).asSequenceBarrier(); final BatchEventProcessor processor = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, handlerWithBarrier); disruptor.handleEventsWith(processor); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch, delayedEventHandler); assertThat(executor.getExecutionCount(), equalTo(2)); } @Test public void shouldSupportCustomProcessorsAndHandlersAsDependencies() throws Exception { final DelayedEventHandler delayedEventHandler1 = createDelayedEventHandler(); final DelayedEventHandler delayedEventHandler2 = createDelayedEventHandler(); disruptor.handleEventsWith(delayedEventHandler1); RingBuffer ringBuffer = disruptor.getRingBuffer(); CountDownLatch countDownLatch = new CountDownLatch(2); EventHandler handlerWithBarrier = new EventHandlerStub<>(countDownLatch); final SequenceBarrier sequenceBarrier = disruptor.after(delayedEventHandler1).asSequenceBarrier(); final BatchEventProcessor processor = new BatchEventProcessorBuilder().build(ringBuffer, sequenceBarrier, delayedEventHandler2); disruptor.after(delayedEventHandler1).and(processor).handleEventsWith(handlerWithBarrier); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch, delayedEventHandler1, delayedEventHandler2); assertThat(executor.getExecutionCount(), equalTo(3)); } @Test public void shouldSupportMultipleCustomProcessorsAsDependencies() throws Exception { final RingBuffer ringBuffer = disruptor.getRingBuffer(); final CountDownLatch countDownLatch = new CountDownLatch(2); final EventHandler handlerWithBarrier = new EventHandlerStub<>(countDownLatch); final DelayedEventHandler delayedEventHandler1 = createDelayedEventHandler(); final BatchEventProcessor processor1 = new BatchEventProcessorBuilder().build(ringBuffer, ringBuffer.newBarrier(), delayedEventHandler1); final DelayedEventHandler delayedEventHandler2 = createDelayedEventHandler(); final BatchEventProcessor processor2 = new BatchEventProcessorBuilder().build(ringBuffer, ringBuffer.newBarrier(), delayedEventHandler2); disruptor.handleEventsWith(processor1, processor2); disruptor.after(processor1, processor2).handleEventsWith(handlerWithBarrier); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch, delayedEventHandler1, delayedEventHandler2); assertThat(executor.getExecutionCount(), equalTo(3)); } @Test @Timeout(value = 2000, unit = MILLISECONDS) public void shouldThrowTimeoutExceptionIfShutdownDoesNotCompleteNormally() { assertThrows(TimeoutException.class, () -> { //Given final DelayedEventHandler delayedEventHandler = createDelayedEventHandler(); disruptor.handleEventsWith(delayedEventHandler); publishEvent(); //Then disruptor.shutdown(1, SECONDS); }); } @Test @Timeout(value = 1, unit = SECONDS) public void shouldTrackRemainingCapacity() throws Exception { final long[] remainingCapacity = {-1}; //Given final EventHandler eventHandler = (event, sequence, endOfBatch) -> remainingCapacity[0] = disruptor.getRingBuffer().remainingCapacity(); disruptor.handleEventsWith(eventHandler); //When publishEvent(); //Then while (remainingCapacity[0] == -1) { LockSupport.parkNanos(MILLISECONDS.toNanos(100)); } assertThat(remainingCapacity[0], is(ringBuffer.getBufferSize() - 1L)); assertThat(disruptor.getRingBuffer().remainingCapacity(), is((long) ringBuffer.getBufferSize())); } @Test public void shouldAllowEventHandlerWithSuperType() throws Exception { final CountDownLatch latch = new CountDownLatch(2); final EventHandler objectHandler = new EventHandlerStub<>(latch); disruptor.handleEventsWith(objectHandler); ensureTwoEventsProcessedAccordingToDependencies(latch); } @Test public void shouldAllowChainingEventHandlersWithSuperType() throws Exception { final CountDownLatch latch = new CountDownLatch(2); final DelayedEventHandler delayedEventHandler = createDelayedEventHandler(); final EventHandler objectHandler = new EventHandlerStub<>(latch); disruptor.handleEventsWith(delayedEventHandler).then(objectHandler); ensureTwoEventsProcessedAccordingToDependencies(latch, delayedEventHandler); } @Test public void shouldMakeEntriesAvailableToFirstCustomProcessorsImmediately() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(2); final EventHandler eventHandler = new EventHandlerStub<>(countDownLatch); disruptor.handleEventsWith( (EventProcessorFactory) (ringBuffer, barrierSequences) -> { assertEquals(0, barrierSequences.length, "Should not have had any barrier sequences"); return new BatchEventProcessorBuilder().build( disruptor.getRingBuffer(), ringBuffer.newBarrier( barrierSequences), eventHandler); }); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch); } @Test public void shouldHonourDependenciesForCustomProcessors() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(2); final EventHandler eventHandler = new EventHandlerStub<>(countDownLatch); final DelayedEventHandler delayedEventHandler = createDelayedEventHandler(); final EventProcessorFactory eventProcessorFactory = (ringBuffer, barrierSequences) -> { assertSame(1, barrierSequences.length, "Should have had a barrier sequence"); return new BatchEventProcessorBuilder().build( disruptor.getRingBuffer(), ringBuffer.newBarrier( barrierSequences), eventHandler); }; disruptor.handleEventsWith(delayedEventHandler) .then(eventProcessorFactory); ensureTwoEventsProcessedAccordingToDependencies(countDownLatch, delayedEventHandler); } private void ensureTwoEventsProcessedAccordingToDependencies( final CountDownLatch countDownLatch, final DelayedEventHandler... dependencies) throws InterruptedException, BrokenBarrierException { publishEvent(); publishEvent(); for (DelayedEventHandler dependency : dependencies) { assertThatCountDownLatchEquals(countDownLatch, 2L); dependency.processEvent(); dependency.processEvent(); } assertThatCountDownLatchIsZero(countDownLatch); } private void assertProducerReaches( final StubPublisher stubPublisher, final int expectedPublicationCount, final boolean strict) { long loopStart = System.currentTimeMillis(); while (stubPublisher.getPublicationCount() < expectedPublicationCount && System .currentTimeMillis() - loopStart < 5000) { Thread.yield(); } if (strict) { assertThat(stubPublisher.getPublicationCount(), equalTo(expectedPublicationCount)); } else { final int actualPublicationCount = stubPublisher.getPublicationCount(); final String msg = "Producer reached unexpected count. Expected at least " + expectedPublicationCount + " but only reached " + actualPublicationCount; assertTrue(actualPublicationCount >= expectedPublicationCount, msg); } } private void createDisruptor() { disruptor = new Disruptor<>( TestEvent.EVENT_FACTORY, 4, executor, ProducerType.SINGLE, new BlockingWaitStrategy()); } private void publishEvent() throws InterruptedException, BrokenBarrierException { if (ringBuffer == null) { ringBuffer = disruptor.start(); for (DelayedEventHandler eventHandler : delayedEventHandlers) { eventHandler.awaitStart(); } } disruptor.publishEvent((event, sequence) -> { }); } private Throwable waitFor(final AtomicReference reference) { while (reference.get() == null) { Thread.yield(); } return reference.get(); } private DelayedEventHandler createDelayedEventHandler() { final DelayedEventHandler delayedEventHandler = new DelayedEventHandler(); delayedEventHandlers.add(delayedEventHandler); return delayedEventHandler; } @SuppressWarnings("SameParameterValue") private void assertThatCountDownLatchEquals( final CountDownLatch countDownLatch, final long expectedCountDownValue) { assertThat(countDownLatch.getCount(), equalTo(expectedCountDownValue)); } private void assertThatCountDownLatchIsZero(final CountDownLatch countDownLatch) throws InterruptedException { boolean released = countDownLatch.await(TIMEOUT_IN_SECONDS, SECONDS); assertTrue(released, "Batch handler did not receive entries: " + countDownLatch.getCount()); } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/stubs/DelayedEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl.stubs; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.support.TestEvent; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicBoolean; public class DelayedEventHandler implements EventHandler { private final AtomicBoolean readyToProcessEvent = new AtomicBoolean(false); private volatile boolean stopped = false; private final CyclicBarrier barrier; public DelayedEventHandler(final CyclicBarrier barrier) { this.barrier = barrier; } public DelayedEventHandler() { this(new CyclicBarrier(2)); } @Override public void onEvent(final TestEvent entry, final long sequence, final boolean endOfBatch) throws Exception { waitForAndSetFlag(false); } public void processEvent() { waitForAndSetFlag(true); } public void stopWaiting() { stopped = true; } private void waitForAndSetFlag(final boolean newValue) { while (!stopped && !Thread.currentThread().isInterrupted() && !readyToProcessEvent.compareAndSet(!newValue, newValue)) { Thread.yield(); } } @Override public void onStart() { try { barrier.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (BrokenBarrierException e) { throw new RuntimeException(e); } } @Override public void onShutdown() { } public void awaitStart() throws InterruptedException, BrokenBarrierException { barrier.await(); } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/stubs/EventHandlerStub.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl.stubs; import com.lmax.disruptor.EventHandler; import java.util.concurrent.CountDownLatch; public class EventHandlerStub implements EventHandler { private final CountDownLatch countDownLatch; public EventHandlerStub(final CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void onEvent(final T entry, final long sequence, final boolean endOfBatch) throws Exception { countDownLatch.countDown(); } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/stubs/EvilEqualsEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl.stubs; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.support.TestEvent; public class EvilEqualsEventHandler implements EventHandler { @Override public void onEvent(final TestEvent entry, final long sequence, final boolean endOfBatch) throws Exception { } @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"}) public boolean equals(final Object o) { return true; } public int hashCode() { return 1; } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/stubs/ExceptionThrowingEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl.stubs; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.support.TestEvent; public class ExceptionThrowingEventHandler implements EventHandler { private final RuntimeException testException; public ExceptionThrowingEventHandler(final RuntimeException testException) { this.testException = testException; } @Override public void onEvent(final TestEvent entry, final long sequence, final boolean endOfBatch) throws Exception { throw testException; } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/stubs/SleepingEventHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl.stubs; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.support.TestEvent; public class SleepingEventHandler implements EventHandler { @Override public void onEvent(final TestEvent entry, final long sequence, final boolean endOfBatch) throws Exception { Thread.sleep(1000); } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/stubs/StubExceptionHandler.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl.stubs; import com.lmax.disruptor.ExceptionHandler; import java.util.concurrent.atomic.AtomicReference; public class StubExceptionHandler implements ExceptionHandler { private final AtomicReference exceptionHandled; public StubExceptionHandler(final AtomicReference exceptionHandled) { this.exceptionHandled = exceptionHandled; } @Override public void handleEventException(final Throwable ex, final long sequence, final Object event) { exceptionHandled.set(ex); } @Override public void handleOnStartException(final Throwable ex) { exceptionHandled.set(ex); } @Override public void handleOnShutdownException(final Throwable ex) { exceptionHandled.set(ex); } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/stubs/StubPublisher.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl.stubs; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.support.TestEvent; public class StubPublisher implements Runnable { private volatile boolean running = true; private volatile int publicationCount = 0; private final RingBuffer ringBuffer; public StubPublisher(final RingBuffer ringBuffer) { this.ringBuffer = ringBuffer; } public void run() { while (running) { final long sequence = ringBuffer.next(); //final TestEvent entry = ringBuffer.get(sequence); ringBuffer.publish(sequence); publicationCount++; } } public int getPublicationCount() { return publicationCount; } public void halt() { running = false; } } ================================================ FILE: src/test/java/com/lmax/disruptor/dsl/stubs/StubThreadFactory.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.dsl.stubs; import com.lmax.disruptor.util.DaemonThreadFactory; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import static org.junit.jupiter.api.Assertions.assertFalse; public final class StubThreadFactory implements ThreadFactory, AfterEachCallback { private static final Logger LOGGER = Logger.getLogger(StubThreadFactory.class.getName()); private final DaemonThreadFactory threadFactory = DaemonThreadFactory.INSTANCE; private final Collection threads = new CopyOnWriteArrayList<>(); private final AtomicBoolean ignoreExecutions = new AtomicBoolean(false); private final AtomicInteger executionCount = new AtomicInteger(0); private final List threadErrors = Collections.synchronizedList(new ArrayList<>()); private final List ignoredExceptions = new ArrayList<>(); @Override public Thread newThread(final Runnable command) { executionCount.getAndIncrement(); Runnable toExecute = () -> { try { command.run(); } catch (Throwable t) { threadErrors.add(t); } }; if (ignoreExecutions.get()) { toExecute = new NoOpRunnable(); } final Thread thread = threadFactory.newThread(toExecute); thread.setName(command.toString()); threads.add(thread); return thread; } public void joinAllThreads() { for (Thread thread : threads) { if (thread.isAlive()) { try { thread.interrupt(); thread.join(5000); } catch (InterruptedException e) { e.printStackTrace(); } } assertFalse(thread.isAlive(), "Failed to stop thread: " + thread); } threads.clear(); } public void ignoreExecutions() { ignoreExecutions.set(true); } public int getExecutionCount() { return executionCount.get(); } @Override public void afterEach(final ExtensionContext context) throws Exception { if (!threadErrors.isEmpty()) { for (final Throwable threadError : threadErrors) { boolean ignored = false; for (final IgnoredException ignoredException : ignoredExceptions) { if (threadError.getMessage().equalsIgnoreCase(ignoredException.exceptionMessage)) { LOGGER.info("Ignoring '" + threadError.getMessage() + "' " + "because: " + ignoredException.reason); ignored = true; break; } } if (!ignored) { throw new Exception(threadError); } } } } private static final class IgnoredException { final String exceptionMessage; final String reason; IgnoredException(final String exceptionMessage, final String reason) { this.exceptionMessage = exceptionMessage; this.reason = reason; } } private static final class NoOpRunnable implements Runnable { @Override public void run() { } } } ================================================ FILE: src/test/java/com/lmax/disruptor/support/DummyEventHandler.java ================================================ package com.lmax.disruptor.support; import com.lmax.disruptor.EventHandler; public class DummyEventHandler implements EventHandler { public int startCalls = 0; public int shutdownCalls = 0; public T lastEvent; public long lastSequence; @Override public void onStart() { startCalls++; } @Override public void onShutdown() { shutdownCalls++; } @Override public void onEvent(final T event, final long sequence, final boolean endOfBatch) throws Exception { lastEvent = event; lastSequence = sequence; } } ================================================ FILE: src/test/java/com/lmax/disruptor/support/DummyEventProcessor.java ================================================ package com.lmax.disruptor.support; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SingleProducerSequencer; import java.util.concurrent.atomic.AtomicBoolean; public class DummyEventProcessor implements EventProcessor { private final Sequence sequence; private final AtomicBoolean running = new AtomicBoolean(false); public DummyEventProcessor(final Sequence sequence) { this.sequence = sequence; } public DummyEventProcessor() { this(new Sequence(SingleProducerSequencer.INITIAL_CURSOR_VALUE)); } public void setSequence(final long sequence) { this.sequence.set(sequence); } @Override public Sequence getSequence() { return sequence; } @Override public void halt() { running.set(false); } @Override public boolean isRunning() { return running.get(); } @Override public void run() { if (!running.compareAndSet(false, true)) { throw new IllegalStateException("Already running"); } } } ================================================ FILE: src/test/java/com/lmax/disruptor/support/DummySequenceBarrier.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.AlertException; import com.lmax.disruptor.SequenceBarrier; public class DummySequenceBarrier implements SequenceBarrier { @Override public long waitFor(final long sequence) throws AlertException, InterruptedException { return 0; } @Override public long getCursor() { return 0; } @Override public boolean isAlerted() { return false; } @Override public void alert() { } @Override public void clearAlert() { } @Override public void checkAlert() throws AlertException { } } ================================================ FILE: src/test/java/com/lmax/disruptor/support/DummyWaitStrategy.java ================================================ package com.lmax.disruptor.support; import com.lmax.disruptor.AlertException; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.TimeoutException; import com.lmax.disruptor.WaitStrategy; public class DummyWaitStrategy implements WaitStrategy { public int signalAllWhenBlockingCalls = 0; @Override public long waitFor( final long sequence, final Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException, TimeoutException { return 0; } @Override public void signalAllWhenBlocking() { signalAllWhenBlockingCalls++; } } ================================================ FILE: src/test/java/com/lmax/disruptor/support/LongEvent.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventFactory; public class LongEvent { private long value; public void set(final long value) { this.value = value; } public long get() { return value; } public static final EventFactory FACTORY = () -> new LongEvent(); } ================================================ FILE: src/test/java/com/lmax/disruptor/support/SequenceUpdater.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.WaitStrategy; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; class SequenceUpdater implements Runnable { public final Sequence sequence = new Sequence(); private final CyclicBarrier barrier = new CyclicBarrier(2); private final long sleepTime; private final WaitStrategy waitStrategy; SequenceUpdater(final long sleepTime, final WaitStrategy waitStrategy) { this.sleepTime = sleepTime; this.waitStrategy = waitStrategy; } @Override public void run() { try { barrier.await(); if (0 != sleepTime) { Thread.sleep(sleepTime); } sequence.incrementAndGet(); waitStrategy.signalAllWhenBlocking(); } catch (Exception e) { e.printStackTrace(); } } public void waitForStartup() throws InterruptedException, BrokenBarrierException { barrier.await(); } } ================================================ FILE: src/test/java/com/lmax/disruptor/support/StubEvent.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventTranslatorTwoArg; public final class StubEvent { private int value; private String testString; public static final EventTranslatorTwoArg TRANSLATOR = (event, sequence, arg0, arg1) -> { event.setValue(arg0); event.setTestString(arg1); }; public StubEvent(final int i) { this.value = i; } public void copy(final StubEvent event) { value = event.value; } public int getValue() { return value; } public void setValue(final int value) { this.value = value; } public String getTestString() { return testString; } public void setTestString(final String testString) { this.testString = testString; } public static final EventFactory EVENT_FACTORY = () -> new StubEvent(-1); @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final StubEvent stubEvent = (StubEvent) o; if (value != stubEvent.value) { return false; } return testString != null ? testString.equals(stubEvent.testString) : stubEvent.testString == null; } @Override public int hashCode() { int result = value; result = 31 * result + (testString != null ? testString.hashCode() : 0); return result; } } ================================================ FILE: src/test/java/com/lmax/disruptor/support/TestEvent.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.EventFactory; public final class TestEvent { @Override public String toString() { return "Test Event"; } public static final EventFactory EVENT_FACTORY = TestEvent::new; } ================================================ FILE: src/test/java/com/lmax/disruptor/support/TestWaiter.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SequenceBarrier; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CyclicBarrier; public final class TestWaiter implements Callable> { private final long toWaitForSequence; private final long initialSequence; private final CyclicBarrier cyclicBarrier; private final SequenceBarrier sequenceBarrier; private final RingBuffer ringBuffer; public TestWaiter( final CyclicBarrier cyclicBarrier, final SequenceBarrier sequenceBarrier, final RingBuffer ringBuffer, final long initialSequence, final long toWaitForSequence) { this.cyclicBarrier = cyclicBarrier; this.initialSequence = initialSequence; this.ringBuffer = ringBuffer; this.toWaitForSequence = toWaitForSequence; this.sequenceBarrier = sequenceBarrier; } @Override public List call() throws Exception { cyclicBarrier.await(); sequenceBarrier.waitFor(toWaitForSequence); final List messages = new ArrayList<>(); for (long l = initialSequence; l <= toWaitForSequence; l++) { messages.add(ringBuffer.get(l)); } return messages; } } ================================================ FILE: src/test/java/com/lmax/disruptor/support/WaitStrategyTestUtil.java ================================================ /* * Copyright 2012 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.support; import com.lmax.disruptor.AlertException; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.TimeoutException; import com.lmax.disruptor.WaitStrategy; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; public class WaitStrategyTestUtil { private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(); public static void assertWaitForWithDelayOf(final long sleepTimeMillis, final WaitStrategy waitStrategy) throws InterruptedException, BrokenBarrierException, AlertException, TimeoutException { SequenceUpdater sequenceUpdater = new SequenceUpdater(sleepTimeMillis, waitStrategy); EXECUTOR.execute(sequenceUpdater); sequenceUpdater.waitForStartup(); Sequence cursor = new Sequence(0); long sequence = waitStrategy.waitFor(0, cursor, sequenceUpdater.sequence, new DummySequenceBarrier()); assertThat(sequence, is(0L)); } } ================================================ FILE: src/test/java/com/lmax/disruptor/util/MutableLong.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.util; /** * Holder class for a long value. */ public class MutableLong { private long value = 0L; /** * Default constructor */ public MutableLong() { } /** * Construct the holder with initial value. * * @param initialValue to be initially set. */ public MutableLong(final long initialValue) { this.value = initialValue; } /** * Get the long value. * * @return the long value. */ public long get() { return value; } /** * Set the long value. * * @param value to set. */ public void set(final long value) { this.value = value; } /** * Increments the value */ public void increment() { value++; } } ================================================ FILE: src/test/java/com/lmax/disruptor/util/PaddedLong.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.util; /** * Cache line padded long variable to be used when false sharing maybe an issue. */ public final class PaddedLong extends MutableLong { public volatile long p1, p2, p3, p4, p5, p6 = 7L; /** * Default constructor */ public PaddedLong() { } /** * Construct with an initial value. * * @param initialValue for construction */ public PaddedLong(final long initialValue) { super(initialValue); } public long sumPaddingToPreventOptimisation() { return p1 + p2 + p3 + p4 + p5 + p6; } } ================================================ FILE: src/test/java/com/lmax/disruptor/util/UnsafeAccess.java ================================================ package com.lmax.disruptor.util; import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedExceptionAction; public class UnsafeAccess { private static final sun.misc.Unsafe THE_UNSAFE; static { try { final PrivilegedExceptionAction action = () -> { Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); return (sun.misc.Unsafe) theUnsafe.get(null); }; THE_UNSAFE = AccessController.doPrivileged(action); } catch (Exception e) { throw new RuntimeException("Unable to load unsafe", e); } } /** * Get a handle on the Unsafe instance, used for accessing low-level concurrency * and memory constructs. * * @return The Unsafe */ public static sun.misc.Unsafe getUnsafe() { return THE_UNSAFE; } } ================================================ FILE: src/test/java/com/lmax/disruptor/util/UtilTest.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor.util; import com.lmax.disruptor.Sequence; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public final class UtilTest { @Test public void shouldReturnNextPowerOfTwo() { int powerOfTwo = Util.ceilingNextPowerOfTwo(1000); assertEquals(1024, powerOfTwo); } @Test public void shouldReturnExactPowerOfTwo() { int powerOfTwo = Util.ceilingNextPowerOfTwo(1024); assertEquals(1024, powerOfTwo); } @Test public void shouldReturnMinimumSequence() { final Sequence[] sequences = {new Sequence(7L), new Sequence(3L), new Sequence(12L)}; assertEquals(3L, Util.getMinimumSequence(sequences)); } @Test public void shouldReturnLongMaxWhenNoEventProcessors() { final Sequence[] sequences = new Sequence[0]; assertEquals(Long.MAX_VALUE, Util.getMinimumSequence(sequences)); } @Test void shouldThrowErrorIfValuePassedToLog2FunctionIsNotPositive() { assertThrows(IllegalArgumentException.class, () -> Util.log2(0)); assertThrows(IllegalArgumentException.class, () -> Util.log2(-1)); assertThrows(IllegalArgumentException.class, () -> Util.log2(Integer.MIN_VALUE)); } @Test void shouldCalculateCorrectlyIntegerFlooredLog2() { assertEquals(0, Util.log2(1)); assertEquals(1, Util.log2(2)); assertEquals(1, Util.log2(3)); assertEquals(10, Util.log2(1024)); assertEquals(30, Util.log2(Integer.MAX_VALUE)); } }

When publishing to the RingBuffer, provide an EventTranslator. The RingBuffer will select the next available * event by sequence and provide it to the EventTranslator (which should update the event), before publishing * the sequence update. * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. */ public interface EventTranslator { /** * Translate a data representation into fields set in given event * * @param event into which the data should be translated. * @param sequence that is assigned to event. */ void translateTo(T event, long sequence); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventTranslatorOneArg.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Implementations translate another data representations into events claimed from the {@link RingBuffer} * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. * @param type first user specified argument to the translator. * @see EventTranslator */ public interface EventTranslatorOneArg { /** * Translate a data representation into fields set in given event * * @param event into which the data should be translated. * @param sequence that is assigned to event. * @param arg0 The first user specified argument to the translator */ void translateTo(T event, long sequence, A arg0); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventTranslatorThreeArg.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Implementations translate another data representations into events claimed from the {@link RingBuffer} * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. * @param type first user specified argument to the translator. * @param type second user specified argument to the translator. * @param type third user specified argument to the translator. * @see EventTranslator */ public interface EventTranslatorThreeArg { /** * Translate a data representation into fields set in given event * * @param event into which the data should be translated. * @param sequence that is assigned to event. * @param arg0 The first user specified argument to the translator * @param arg1 The second user specified argument to the translator * @param arg2 The third user specified argument to the translator */ void translateTo(T event, long sequence, A arg0, B arg1, C arg2); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventTranslatorTwoArg.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Implementations translate another data representations into events claimed from the {@link RingBuffer} * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. * @param type first user specified argument to the translator. * @param type second user specified argument to the translator. * @see EventTranslator */ public interface EventTranslatorTwoArg { /** * Translate a data representation into fields set in given event * * @param event into which the data should be translated. * @param sequence that is assigned to event. * @param arg0 The first user specified argument to the translator * @param arg1 The second user specified argument to the translator */ void translateTo(T event, long sequence, A arg0, B arg1); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventTranslatorVararg.java ================================================ /* * Copyright 2011 LMAX Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lmax.disruptor; /** * Implementations translate another data representations into events claimed from the {@link RingBuffer} * * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. * @see EventTranslator */ public interface EventTranslatorVararg { /** * Translate a data representation into fields set in given event * * @param event into which the data should be translated. * @param sequence that is assigned to event. * @param args The array of user arguments. */ void translateTo(T event, long sequence, Object... args); } ================================================ FILE: src/main/java/com/lmax/disruptor/EventuallyGiveUpBatchRewindStrategy.java ================================================ package com.lmax.disruptor; /** *