├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE.md ├── README.md ├── build.gradle ├── db-queue-brave ├── build.gradle ├── coverage.properties ├── src │ ├── main │ │ └── java │ │ │ └── ru │ │ │ └── yoomoney │ │ │ └── tech │ │ │ └── dbqueue │ │ │ └── brave │ │ │ ├── B3SingleFormatSpanConverter.java │ │ │ ├── TracingQueueProducer.java │ │ │ └── TracingTaskLifecycleListener.java │ └── test │ │ └── java │ │ └── ru │ │ └── yoomoney │ │ └── tech │ │ └── dbqueue │ │ └── brave │ │ ├── B3SingleFormatSpanConverterTest.java │ │ ├── TracingQueueProducerTest.java │ │ └── TracingTaskLifecycleListenerTest.java └── static-analysis.properties ├── db-queue-core ├── build.gradle ├── coverage.properties ├── src │ ├── main │ │ └── java │ │ │ └── ru │ │ │ └── yoomoney │ │ │ └── tech │ │ │ └── dbqueue │ │ │ ├── api │ │ │ ├── EnqueueParams.java │ │ │ ├── EnqueueResult.java │ │ │ ├── QueueConsumer.java │ │ │ ├── QueueProducer.java │ │ │ ├── QueueShardRouter.java │ │ │ ├── Task.java │ │ │ ├── TaskExecutionResult.java │ │ │ ├── TaskPayloadTransformer.java │ │ │ ├── TaskRecord.java │ │ │ └── impl │ │ │ │ ├── MonitoringQueueProducer.java │ │ │ │ ├── NoopPayloadTransformer.java │ │ │ │ ├── ShardingQueueProducer.java │ │ │ │ └── SingleQueueShardRouter.java │ │ │ ├── config │ │ │ ├── DatabaseAccessLayer.java │ │ │ ├── DatabaseDialect.java │ │ │ ├── QueueExecutionPool.java │ │ │ ├── QueueService.java │ │ │ ├── QueueShard.java │ │ │ ├── QueueShardId.java │ │ │ ├── QueueTableSchema.java │ │ │ ├── QueueThreadFactory.java │ │ │ ├── TaskLifecycleListener.java │ │ │ ├── ThreadLifecycleListener.java │ │ │ └── impl │ │ │ │ ├── CompositeTaskLifecycleListener.java │ │ │ │ ├── CompositeThreadLifecycleListener.java │ │ │ │ ├── LoggingTaskLifecycleListener.java │ │ │ │ ├── LoggingThreadLifecycleListener.java │ │ │ │ ├── NoopTaskLifecycleListener.java │ │ │ │ └── NoopThreadLifecycleListener.java │ │ │ ├── dao │ │ │ ├── QueueDao.java │ │ │ └── QueuePickTaskDao.java │ │ │ ├── internal │ │ │ ├── package-info.java │ │ │ ├── processing │ │ │ │ ├── MillisTimeProvider.java │ │ │ │ ├── QueueLoop.java │ │ │ │ ├── QueueProcessingStatus.java │ │ │ │ ├── QueueTaskPoller.java │ │ │ │ ├── ReenqueueRetryStrategy.java │ │ │ │ ├── TaskPicker.java │ │ │ │ ├── TaskProcessor.java │ │ │ │ ├── TaskResultHandler.java │ │ │ │ └── TimeLimiter.java │ │ │ └── runner │ │ │ │ ├── BaseQueueRunner.java │ │ │ │ ├── QueueRunner.java │ │ │ │ ├── QueueRunnerInExternalExecutor.java │ │ │ │ ├── QueueRunnerInSeparateTransactions.java │ │ │ │ └── QueueRunnerInTransaction.java │ │ │ └── settings │ │ │ ├── DynamicSetting.java │ │ │ ├── ExtSettings.java │ │ │ ├── FailRetryType.java │ │ │ ├── FailureSettings.java │ │ │ ├── FailureSettingsParser.java │ │ │ ├── FileWatcher.java │ │ │ ├── PollSettings.java │ │ │ ├── PollSettingsParser.java │ │ │ ├── ProcessingMode.java │ │ │ ├── ProcessingSettings.java │ │ │ ├── ProcessingSettingsParser.java │ │ │ ├── QueueConfig.java │ │ │ ├── QueueConfigsReader.java │ │ │ ├── QueueConfigsReloader.java │ │ │ ├── QueueId.java │ │ │ ├── QueueLocation.java │ │ │ ├── QueueLocationParser.java │ │ │ ├── QueueSettings.java │ │ │ ├── ReenqueueRetryType.java │ │ │ ├── ReenqueueSettings.java │ │ │ └── ReenqueueSettingsParser.java │ └── test │ │ ├── java │ │ └── ru │ │ │ └── yoomoney │ │ │ └── tech │ │ │ └── dbqueue │ │ │ ├── ArchitectureTest.java │ │ │ ├── api │ │ │ ├── EnqueueParamsTest.java │ │ │ ├── QueueShardIdTest.java │ │ │ ├── TaskExecutionResultTest.java │ │ │ ├── TaskRecordTest.java │ │ │ ├── TaskTest.java │ │ │ └── impl │ │ │ │ ├── MonitoringQueueProducerTest.java │ │ │ │ ├── ShardingQueueProducerTest.java │ │ │ │ └── SingleQueueShardRouterTest.java │ │ │ ├── config │ │ │ ├── DirectExecutor.java │ │ │ ├── QueueExecutionPoolTest.java │ │ │ ├── QueueServiceTest.java │ │ │ ├── QueueTableSchemaTest.java │ │ │ └── impl │ │ │ │ ├── CompositeTaskLifecycleListenerTest.java │ │ │ │ ├── CompositeThreadLifecycleListenerTest.java │ │ │ │ ├── LoggingTaskLifecycleListenerTest.java │ │ │ │ └── LoggingThreadLifecycleListenerTest.java │ │ │ ├── internal │ │ │ ├── processing │ │ │ │ ├── DelegatedSingleQueueLoopExecution.java │ │ │ │ ├── QueueTaskPollerTest.java │ │ │ │ ├── ReenqueueRetryStrategyTest.java │ │ │ │ ├── SyncQueueLoop.java │ │ │ │ ├── TaskPickerTest.java │ │ │ │ ├── TaskProcessorTest.java │ │ │ │ ├── TaskResultHandlerTest.java │ │ │ │ └── TimeLimiterTest.java │ │ │ └── runner │ │ │ │ ├── QueueRunnerInExternalExecutorTest.java │ │ │ │ ├── QueueRunnerInSeparateTransactionsTest.java │ │ │ │ ├── QueueRunnerInTransactionTest.java │ │ │ │ └── QueueRunnerSpringQueuePickTaskDaoQueueDaoFactoryTest.java │ │ │ ├── settings │ │ │ ├── DynamicSettingTest.java │ │ │ ├── ExtSettingsTest.java │ │ │ ├── FailureSettingsTest.java │ │ │ ├── FileWatcherTest.java │ │ │ ├── PollSettingsTest.java │ │ │ ├── ProcessingSettingsTest.java │ │ │ ├── QueueConfigTest.java │ │ │ ├── QueueConfigsReaderTest.java │ │ │ ├── QueueConfigsReloaderTest.java │ │ │ ├── QueueIdTest.java │ │ │ ├── QueueLocationTest.java │ │ │ ├── QueueSettingsTest.java │ │ │ └── ReenqueueSettingsTest.java │ │ │ └── stub │ │ │ ├── FakeMillisTimeProvider.java │ │ │ ├── FakeQueueConsumer.java │ │ │ ├── NoopQueueConsumer.java │ │ │ ├── StringQueueConsumer.java │ │ │ ├── StubDatabaseAccessLayer.java │ │ │ └── TestFixtures.java │ │ └── resources │ │ └── log4j2.xml └── static-analysis.properties ├── db-queue-spring ├── build.gradle ├── coverage.properties ├── src │ ├── main │ │ └── java │ │ │ └── ru │ │ │ └── yoomoney │ │ │ └── tech │ │ │ └── dbqueue │ │ │ └── spring │ │ │ └── dao │ │ │ ├── H2QueueDao.java │ │ │ ├── H2QueuePickTaskDao.java │ │ │ ├── MssqlQueueDao.java │ │ │ ├── MssqlQueuePickTaskDao.java │ │ │ ├── Oracle11QueueDao.java │ │ │ ├── Oracle11QueuePickTaskDao.java │ │ │ ├── PostgresQueueDao.java │ │ │ ├── PostgresQueuePickTaskDao.java │ │ │ └── SpringDatabaseAccessLayer.java │ └── test │ │ ├── java │ │ └── ru │ │ │ └── yoomoney │ │ │ └── tech │ │ │ └── dbqueue │ │ │ └── spring │ │ │ └── dao │ │ │ ├── CustomH2PickTaskDaoTest.java │ │ │ ├── CustomH2QueueDaoTest.java │ │ │ ├── CustomMssqlQueueDaoTest.java │ │ │ ├── CustomMssqlQueuePickTaskDaoTest.java │ │ │ ├── CustomOracle11QueueDaoTest.java │ │ │ ├── CustomOracle11QueuePickTaskDaoTest.java │ │ │ ├── CustomPostgresQueueDaoTest.java │ │ │ ├── CustomPostgresQueuePickTaskDaoTest.java │ │ │ ├── DefaultH2QueueDaoTest.java │ │ │ ├── DefaultH2QueuePickTaskDaoTest.java │ │ │ ├── DefaultH2WithSequenceQueueDaoTest.java │ │ │ ├── DefaultMssqlQueueDaoTest.java │ │ │ ├── DefaultMssqlQueuePickTaskDaoTest.java │ │ │ ├── DefaultMssqlWithSequenceQueueDaoTest.java │ │ │ ├── DefaultOracle11QueueDaoTest.java │ │ │ ├── DefaultOracle11QueuePickTaskDaoTest.java │ │ │ ├── DefaultPostgresQueueDaoTest.java │ │ │ ├── DefaultPostgresQueuePickTaskDaoTest.java │ │ │ ├── DefaultPostgresWithSequenceQueueDaoTest.java │ │ │ ├── QueueDaoTest.java │ │ │ ├── QueuePickTaskDaoTest.java │ │ │ └── utils │ │ │ ├── H2DatabaseInitializer.java │ │ │ ├── MssqlDatabaseInitializer.java │ │ │ ├── OracleDatabaseInitializer.java │ │ │ └── PostgresDatabaseInitializer.java │ │ └── resources │ │ ├── container-license-acceptance.txt │ │ └── log4j2.xml └── static-analysis.properties ├── db-queue-test ├── build.gradle └── src │ └── test │ ├── java │ └── ru │ │ └── yoomoney │ │ └── tech │ │ └── dbqueue │ │ └── test │ │ ├── DefaultDatabaseInitializer.java │ │ ├── ExampleBasicConfiguration.java │ │ ├── ExampleTracingConfiguration.java │ │ └── StringQueueConsumer.java │ └── resources │ └── log4j2.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── project.gradle └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | **/gradlew text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.orig 3 | target/ 4 | bin/ 5 | .gradle/ 6 | build/ 7 | temp/ 8 | tmp/ 9 | .idea/ 10 | *.iws 11 | *.iml 12 | *.ipr 13 | *.ids 14 | out/ 15 | *.log 16 | classes 17 | git_key 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | import: yoomoney/travis-shared-configuration:build-and-publish-plugin.yml@master -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @yoomoney/backend-experts -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 NBCO YooMoney LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import ru.yoomoney.gradle.plugins.backend.build.JavaExtension 2 | import ru.yoomoney.gradle.plugins.javapublishing.JavaArtifactPublishExtension 3 | 4 | buildscript { 5 | apply from: 'project.gradle', to: buildscript 6 | } 7 | 8 | apply plugin: 'ru.yoomoney.gradle.plugins.library-project-plugin' 9 | artifactId = 'db-queue' 10 | 11 | dependencies { 12 | 13 | compile project(':db-queue-core'), 14 | project(':db-queue-spring') 15 | 16 | } 17 | 18 | def rootJavaExtension = project.extensions.getByType(JavaExtension.class) 19 | def rootJavaArtifactPublishExtension = project.extensions.getByType(JavaArtifactPublishExtension.class) 20 | 21 | subprojects { 22 | apply plugin: 'ru.yoomoney.gradle.plugins.java-plugin' 23 | 24 | javaModule { 25 | repositories = rootJavaExtension.repositories 26 | snapshotsRepositories = rootJavaExtension.snapshotsRepositories 27 | } 28 | 29 | rootProject.tasks.getByName('build').dependsOn(tasks.getByName('build')) 30 | 31 | apply plugin: 'ru.yoomoney.gradle.plugins.java-artifact-publish-plugin' 32 | 33 | javaArtifactPublishSettings { 34 | artifactId = "${project.name}" 35 | groupId = 'ru.yoomoney.tech' 36 | 37 | nexusUser = rootJavaArtifactPublishExtension.nexusUser 38 | nexusPassword = rootJavaArtifactPublishExtension.nexusPassword 39 | snapshotRepository = rootJavaArtifactPublishExtension.snapshotRepository 40 | releaseRepository = rootJavaArtifactPublishExtension.releaseRepository 41 | signing = rootJavaArtifactPublishExtension.signing 42 | staging = rootJavaArtifactPublishExtension.staging 43 | publicationAdditionalInfo = rootJavaArtifactPublishExtension.publicationAdditionalInfo 44 | } 45 | 46 | rootProject.tasks.getByName('publish').dependsOn(tasks.getByName('publish')) 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /db-queue-brave/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: "$rootProject.projectDir/project.gradle", to: buildscript 3 | } 4 | 5 | dependencies { 6 | 7 | compile project(':db-queue-core'), 8 | 'io.zipkin.brave:brave:5.10.2' 9 | 10 | compileOnly 'com.google.code.findbugs:jsr305:3.0.1', 11 | 'com.google.code.findbugs:annotations:3.0.1' 12 | 13 | testCompile 'junit:junit:4.13.2', 14 | 'org.mockito:mockito-core:4.0.0', 15 | 'org.apache.logging.log4j:log4j-core:2.17.1', 16 | 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1' 17 | 18 | testCompileOnly 'com.google.code.findbugs:jsr305:3.0.1', 19 | 'com.google.code.findbugs:annotations:3.0.1' 20 | } 21 | -------------------------------------------------------------------------------- /db-queue-brave/coverage.properties: -------------------------------------------------------------------------------- 1 | instruction=96 2 | branch=100 3 | method=84 4 | class=100 5 | -------------------------------------------------------------------------------- /db-queue-brave/src/main/java/ru/yoomoney/tech/dbqueue/brave/TracingTaskLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.brave; 2 | 3 | import brave.Span; 4 | import brave.Tracer; 5 | import brave.Tracing; 6 | import brave.propagation.TraceContextOrSamplingFlags; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 10 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 11 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 12 | import ru.yoomoney.tech.dbqueue.config.TaskLifecycleListener; 13 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 14 | 15 | import javax.annotation.Nonnull; 16 | import javax.annotation.Nullable; 17 | import java.util.Objects; 18 | 19 | /** 20 | * Task lifecycle listener with brave tracing support 21 | * 22 | * @author Oleg Kandaurov 23 | * @since 11.06.2021 24 | */ 25 | public class TracingTaskLifecycleListener implements TaskLifecycleListener { 26 | 27 | private static final Logger log = LoggerFactory.getLogger(TracingTaskLifecycleListener.class); 28 | 29 | private static final ThreadLocal threadLocalSpan = new ThreadLocal<>(); 30 | 31 | @Nonnull 32 | private final Tracing tracing; 33 | @Nonnull 34 | private final B3SingleFormatSpanConverter spanConverter; 35 | private final String traceField; 36 | 37 | public TracingTaskLifecycleListener(@Nonnull Tracing tracing, String traceField) { 38 | this.tracing = tracing; 39 | this.spanConverter = new B3SingleFormatSpanConverter(tracing); 40 | this.traceField = traceField; 41 | } 42 | 43 | @Override 44 | public void picked(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord, long pickTaskTime) { 45 | } 46 | 47 | @Override 48 | public void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord) { 49 | String queueName = location.getQueueId().asString(); 50 | String traceInfo = taskRecord.getExtData().get(traceField); 51 | Span taskSpan = spanConverter.deserializeTraceContext(traceInfo) 52 | .map(ctx -> tracing.tracer().nextSpan(TraceContextOrSamplingFlags.create(ctx))) 53 | .orElseGet(() -> { 54 | log.info("unknown trace context, creating new"); 55 | return tracing.tracer().newTrace(); 56 | }); 57 | taskSpan.name("qreceive " + queueName) 58 | .tag("queue.name", queueName) 59 | .tag("queue.operation", "receive") 60 | .kind(Span.Kind.CONSUMER); 61 | 62 | threadLocalSpan.set(new SpanAndScope(taskSpan, tracing.tracer().withSpanInScope(taskSpan.start()))); 63 | } 64 | 65 | @Override 66 | public void executed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord, 67 | @Nonnull TaskExecutionResult executionResult, long processTaskTime) { 68 | } 69 | 70 | @Override 71 | public void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord) { 72 | SpanAndScope spanAndScope = threadLocalSpan.get(); 73 | threadLocalSpan.remove(); 74 | spanAndScope.spanInScope.close(); 75 | spanAndScope.span.finish(); 76 | } 77 | 78 | @Override 79 | public void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord, 80 | @Nullable Exception exc) { 81 | threadLocalSpan.get().span.error(exc); 82 | } 83 | 84 | private static final class SpanAndScope { 85 | @Nonnull 86 | final Span span; 87 | @Nonnull 88 | final Tracer.SpanInScope spanInScope; 89 | 90 | SpanAndScope(@Nonnull Span span, @Nonnull Tracer.SpanInScope spanInScope) { 91 | this.span = Objects.requireNonNull(span); 92 | this.spanInScope = Objects.requireNonNull(spanInScope); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /db-queue-brave/src/test/java/ru/yoomoney/tech/dbqueue/brave/TracingTaskLifecycleListenerTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.brave; 2 | 3 | import brave.Span; 4 | import brave.Tracing; 5 | import brave.propagation.B3Propagation; 6 | import brave.propagation.ExtraFieldPropagation; 7 | import brave.propagation.TraceContext; 8 | import org.junit.Test; 9 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 10 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 11 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 12 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 13 | 14 | import javax.annotation.Nonnull; 15 | import java.util.LinkedHashMap; 16 | import java.util.Map; 17 | 18 | import static org.hamcrest.CoreMatchers.equalTo; 19 | import static org.hamcrest.CoreMatchers.notNullValue; 20 | import static org.hamcrest.MatcherAssert.assertThat; 21 | 22 | /** 23 | * @author Oleg Kandaurov 24 | * @since 11.06.2021 25 | */ 26 | public class TracingTaskLifecycleListenerTest { 27 | 28 | @Nonnull 29 | private Tracing createTracing() { 30 | return Tracing.newBuilder().propagationFactory( 31 | ExtraFieldPropagation.newFactoryBuilder( 32 | B3Propagation.newFactoryBuilder() 33 | .injectFormat(Span.Kind.PRODUCER, B3Propagation.Format.SINGLE) 34 | .injectFormat(Span.Kind.CONSUMER, B3Propagation.Format.SINGLE) 35 | .build()).addField("extra-field").build()) 36 | .build(); 37 | } 38 | 39 | @Test 40 | public void should_start_span() { 41 | Tracing tracing = createTracing(); 42 | QueueShardId shardId = new QueueShardId("s1"); 43 | QueueLocation location = QueueLocation.builder().withTableName("table").withQueueId(new QueueId("testqueue")).build(); 44 | Map extData = new LinkedHashMap<>(); 45 | extData.put("trace_info", "b3=0000000000000001-0000000000000002-1"); 46 | TaskRecord taskRecord = TaskRecord.builder() 47 | .withId(1).withReenqueueAttemptsCount(2).withTotalAttemptsCount(2) 48 | .withExtData(extData).build(); 49 | 50 | TracingTaskLifecycleListener taskListener = new TracingTaskLifecycleListener(tracing, "trace_info"); 51 | 52 | taskListener.started(shardId, location, taskRecord); 53 | 54 | TraceContext traceContext = tracing.tracer().currentSpan().context(); 55 | assertThat(traceContext.traceId(), equalTo(1L)); 56 | assertThat(traceContext.parentId(), equalTo(2L)); 57 | assertThat(traceContext.sampled(), equalTo(true)); 58 | taskListener.finished(shardId, location, taskRecord); 59 | } 60 | 61 | @Test 62 | public void should_start_valid_root_span() { 63 | Tracing tracing = createTracing(); 64 | QueueShardId shardId = new QueueShardId("s1"); 65 | QueueLocation location = QueueLocation.builder().withTableName("table").withQueueId(new QueueId("testqueue")).build(); 66 | Map extData = new LinkedHashMap<>(); 67 | extData.put("trace_info", "invalid"); 68 | TaskRecord taskRecord = TaskRecord.builder() 69 | .withId(1).withReenqueueAttemptsCount(2).withTotalAttemptsCount(2) 70 | .withExtData(extData).build(); 71 | 72 | TracingTaskLifecycleListener taskListener = new TracingTaskLifecycleListener(tracing, "trace_info"); 73 | 74 | taskListener.started(shardId, location, taskRecord); 75 | 76 | TraceContext traceContext = tracing.tracer().currentSpan().context(); 77 | assertThat(traceContext.traceId(), notNullValue()); 78 | assertThat(traceContext.parentIdString(), equalTo(null)); 79 | taskListener.finished(shardId, location, taskRecord); 80 | } 81 | } -------------------------------------------------------------------------------- /db-queue-brave/static-analysis.properties: -------------------------------------------------------------------------------- 1 | checkstyle=0 2 | -------------------------------------------------------------------------------- /db-queue-core/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: "$rootProject.projectDir/project.gradle", to: buildscript 3 | } 4 | 5 | dependencies { 6 | 7 | compile 'org.slf4j:slf4j-api:1.7.36' 8 | 9 | compileOnly 'com.google.code.findbugs:jsr305:3.0.1', 10 | 'com.google.code.findbugs:annotations:3.0.1' 11 | 12 | testCompile 'junit:junit:4.13.2', 13 | 'org.apache.logging.log4j:log4j-core:2.17.1', 14 | 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1', 15 | 'org.mockito:mockito-core:4.0.0', 16 | 'com.tngtech.archunit:archunit:0.9.1', 17 | 'nl.jqno.equalsverifier:equalsverifier:3.9' 18 | 19 | 20 | testCompileOnly 'com.google.code.findbugs:jsr305:3.0.1', 21 | 'com.google.code.findbugs:annotations:3.0.1' 22 | } 23 | -------------------------------------------------------------------------------- /db-queue-core/coverage.properties: -------------------------------------------------------------------------------- 1 | instruction=87 2 | branch=82 3 | method=86 4 | class=94 5 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/EnqueueResult.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 4 | 5 | import javax.annotation.Nonnull; 6 | import java.util.Objects; 7 | 8 | /** 9 | * Task enqueue result 10 | * 11 | * @author Oleg Kandaurov 12 | * @since 11.06.2021 13 | */ 14 | public class EnqueueResult { 15 | @Nonnull 16 | private final QueueShardId shardId; 17 | @Nonnull 18 | private final Long enqueueId; 19 | 20 | /** 21 | * Constructor 22 | * 23 | * @param shardId shard id 24 | * @param enqueueId sequence id 25 | */ 26 | public EnqueueResult(@Nonnull QueueShardId shardId, @Nonnull Long enqueueId) { 27 | this.shardId = Objects.requireNonNull(shardId); 28 | this.enqueueId = Objects.requireNonNull(enqueueId); 29 | } 30 | 31 | /** 32 | * Shard identifier of added task 33 | * 34 | * @return shard id 35 | */ 36 | @Nonnull 37 | public QueueShardId getShardId() { 38 | return shardId; 39 | } 40 | 41 | /** 42 | * Identifier (sequence id) of added task 43 | * 44 | * @return sequence id 45 | */ 46 | @Nonnull 47 | public Long getEnqueueId() { 48 | return enqueueId; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "EnqueueResult{" + 54 | "shardId=" + shardId + 55 | ", enqueueId=" + enqueueId + 56 | '}'; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object obj) { 61 | if (this == obj) { 62 | return true; 63 | } 64 | if (obj == null || getClass() != obj.getClass()) { 65 | return false; 66 | } 67 | EnqueueResult that = (EnqueueResult) obj; 68 | return shardId.equals(that.shardId) && enqueueId.equals(that.enqueueId); 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | return Objects.hash(shardId, enqueueId); 74 | } 75 | 76 | /** 77 | * Creates builder for {@link EnqueueResult} object 78 | * 79 | * @return builder 80 | */ 81 | public static Builder builder() { 82 | return new Builder(); 83 | } 84 | 85 | /** 86 | * Builder for the {@link EnqueueResult} object. 87 | */ 88 | public static class Builder { 89 | 90 | private QueueShardId shardId; 91 | private Long enqueueId; 92 | 93 | private Builder() { 94 | } 95 | 96 | /** 97 | * Set shard identifier of added task 98 | * 99 | * @param shardId shard identifier of added task 100 | * @return Builder 101 | */ 102 | public Builder withShardId(@Nonnull QueueShardId shardId) { 103 | this.shardId = Objects.requireNonNull(shardId, "shardId must not be null"); 104 | return this; 105 | } 106 | 107 | /** 108 | * Set identifier (sequence id) of added task 109 | * 110 | * @param enqueueId sequence id 111 | * @return Builder 112 | */ 113 | public Builder withEnqueueId(@Nonnull Long enqueueId) { 114 | this.enqueueId = Objects.requireNonNull(enqueueId, "enqueueId must not be null"); 115 | return this; 116 | } 117 | 118 | public EnqueueResult build() { 119 | return new EnqueueResult(shardId, enqueueId); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/QueueConsumer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import ru.yoomoney.tech.dbqueue.settings.ProcessingMode; 4 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 5 | 6 | import javax.annotation.Nonnull; 7 | import java.util.Optional; 8 | import java.util.concurrent.Executor; 9 | 10 | /** 11 | * Task processor for the queue 12 | * 13 | * @param The type of the payload in the task 14 | * @author Oleg Kandaurov 15 | * @since 09.07.2017 16 | */ 17 | public interface QueueConsumer { 18 | 19 | /** 20 | * Process the task from the queue 21 | * 22 | * @param task A typed task for processing 23 | * @return A result of task processing 24 | */ 25 | @Nonnull 26 | TaskExecutionResult execute(@Nonnull Task task); 27 | 28 | /** 29 | * Get queue configuration 30 | * 31 | * @return Queue configuration 32 | */ 33 | @Nonnull 34 | QueueConfig getQueueConfig(); 35 | 36 | /** 37 | * Get task payload transformer, which transform the task's {@linkplain String} payload into the type of the task 38 | * 39 | * @return Task payload transformer 40 | */ 41 | @Nonnull 42 | TaskPayloadTransformer getPayloadTransformer(); 43 | 44 | /** 45 | * Task executor for {@link ProcessingMode#USE_EXTERNAL_EXECUTOR} mode. 46 | * Applies only to that mode 47 | * 48 | * @return {@linkplain Optional} of external task executor 49 | */ 50 | default Optional getExecutor() { 51 | return Optional.empty(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/QueueProducer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * Task producer for the queue, which adds a new task into the queue. 7 | * 8 | * @param The type of the payload in the task 9 | * @author Oleg Kandaurov 10 | * @since 10.07.2017 11 | */ 12 | public interface QueueProducer { 13 | 14 | /** 15 | * Add a new task into the queue 16 | * 17 | * @param enqueueParams Parameters with typed payload to enqueue the task 18 | * @return Enqueue result 19 | */ 20 | EnqueueResult enqueue(@Nonnull EnqueueParams enqueueParams); 21 | 22 | /** 23 | * Get task payload transformer, which transform the task's payload into the {@linkplain String} 24 | * 25 | * @return Task payload transformer 26 | */ 27 | @Nonnull 28 | TaskPayloadTransformer getPayloadTransformer(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/QueueShardRouter.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import ru.yoomoney.tech.dbqueue.config.DatabaseAccessLayer; 4 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 5 | 6 | /** 7 | * Dispatcher for sharding support. 8 | *

9 | * It evaluates designated shard based on task parameters. 10 | * 11 | * @param The type of the payload in the task 12 | * @param The type of the database access layer 13 | * @author Oleg Kandaurov 14 | * @since 11.06.2021 15 | */ 16 | public interface QueueShardRouter { 17 | 18 | /** 19 | * Get designated shard for task parameters 20 | * 21 | * @param enqueueParams Parameters with typed payload to enqueue the task 22 | * @return Shard where task will be processed on 23 | */ 24 | QueueShard resolveShard(EnqueueParams enqueueParams); 25 | } 26 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/TaskPayloadTransformer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | /** 6 | * Marshaller and unmarshaller for the payload in the task 7 | * 8 | * @param The type of the payload in the task 9 | * @author Oleg Kandaurov 10 | * @since 10.07.2017 11 | */ 12 | public interface TaskPayloadTransformer { 13 | 14 | /** 15 | * Unmarshall the string payload from the task into the object with task data 16 | * 17 | * @param payload task payload 18 | * @return Object with task data 19 | */ 20 | @Nullable 21 | PayloadT toObject(@Nullable String payload); 22 | 23 | /** 24 | * Marshall the typed object with task parameters into string payload. 25 | * 26 | * @param payload task payload 27 | * @return string with the task payload. 28 | */ 29 | @Nullable 30 | String fromObject(@Nullable PayloadT payload); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/impl/MonitoringQueueProducer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import ru.yoomoney.tech.dbqueue.api.EnqueueParams; 6 | import ru.yoomoney.tech.dbqueue.api.EnqueueResult; 7 | import ru.yoomoney.tech.dbqueue.api.QueueProducer; 8 | import ru.yoomoney.tech.dbqueue.api.TaskPayloadTransformer; 9 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 10 | 11 | import javax.annotation.Nonnull; 12 | import java.time.Clock; 13 | import java.util.Objects; 14 | import java.util.function.BiConsumer; 15 | 16 | /** 17 | * Wrapper for queue producer with logging and monitoring support 18 | * 19 | * @param The type of the payload in the task 20 | * @author Oleg Kandaurov 21 | * @since 11.06.2021 22 | */ 23 | public class MonitoringQueueProducer implements QueueProducer { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(MonitoringQueueProducer.class); 26 | 27 | @Nonnull 28 | private final QueueProducer queueProducer; 29 | @Nonnull 30 | private final QueueId queueId; 31 | @Nonnull 32 | private final BiConsumer monitoringCallback; 33 | @Nonnull 34 | private final Clock clock; 35 | 36 | /** 37 | * Constructor 38 | * 39 | * @param queueProducer Task producer for the queue 40 | * @param queueId Id of the queue 41 | * @param monitoringCallback Callback invoked after putting a task in the queue. 42 | * It might help to monitor enqueue time. 43 | * @param clock A clock to mock current time 44 | */ 45 | MonitoringQueueProducer(@Nonnull QueueProducer queueProducer, 46 | @Nonnull QueueId queueId, 47 | @Nonnull BiConsumer monitoringCallback, 48 | @Nonnull Clock clock) { 49 | this.queueProducer = Objects.requireNonNull(queueProducer); 50 | this.queueId = Objects.requireNonNull(queueId); 51 | this.monitoringCallback = Objects.requireNonNull(monitoringCallback); 52 | this.clock = Objects.requireNonNull(clock); 53 | } 54 | 55 | /** 56 | * Constructor 57 | * 58 | * @param queueProducer Task producer for the queue 59 | * @param queueId Id of the queue 60 | * @param monitoringCallback Callback invoked after putting a task in the queue. 61 | * It might help to monitor enqueue time. 62 | */ 63 | public MonitoringQueueProducer(@Nonnull QueueProducer queueProducer, 64 | @Nonnull QueueId queueId, 65 | @Nonnull BiConsumer monitoringCallback) { 66 | this(queueProducer, queueId, monitoringCallback, Clock.systemDefaultZone()); 67 | } 68 | 69 | /** 70 | * Constructor 71 | * 72 | * @param queueProducer Task producer for the queue 73 | * @param queueId Id of the queue 74 | */ 75 | public MonitoringQueueProducer(@Nonnull QueueProducer queueProducer, 76 | @Nonnull QueueId queueId) { 77 | this(queueProducer, queueId, ((enqueueResult, id) -> { 78 | })); 79 | } 80 | 81 | @Override 82 | public EnqueueResult enqueue(@Nonnull EnqueueParams enqueueParams) { 83 | log.info("enqueuing task: queue={}, delay={}", queueId, enqueueParams.getExecutionDelay()); 84 | long startTime = clock.millis(); 85 | EnqueueResult enqueueResult = queueProducer.enqueue(enqueueParams); 86 | log.info("task enqueued: id={}, queueShardId={}", enqueueResult.getEnqueueId(), enqueueResult.getShardId()); 87 | long elapsedTime = clock.millis() - startTime; 88 | monitoringCallback.accept(enqueueResult, elapsedTime); 89 | return enqueueResult; 90 | } 91 | 92 | @Nonnull 93 | @Override 94 | public TaskPayloadTransformer getPayloadTransformer() { 95 | return queueProducer.getPayloadTransformer(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/impl/NoopPayloadTransformer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api.impl; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.TaskPayloadTransformer; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | /** 8 | * Default payload transformer, which performs no transformation 9 | * and returns the same string as in the raw payload. 10 | *

11 | * Use where no transformation required. 12 | */ 13 | public final class NoopPayloadTransformer implements TaskPayloadTransformer { 14 | 15 | private static final NoopPayloadTransformer INSTANCE = new NoopPayloadTransformer(); 16 | 17 | /** 18 | * Get payload transformer instance. 19 | * 20 | * @return Singleton of transformer. 21 | */ 22 | public static NoopPayloadTransformer getInstance() { 23 | return INSTANCE; 24 | } 25 | 26 | private NoopPayloadTransformer() { 27 | } 28 | 29 | @Nullable 30 | @Override 31 | public String toObject(@Nullable String payload) { 32 | return payload; 33 | } 34 | 35 | @Nullable 36 | @Override 37 | public String fromObject(@Nullable String payload) { 38 | return payload; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/impl/ShardingQueueProducer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api.impl; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.EnqueueParams; 4 | import ru.yoomoney.tech.dbqueue.api.EnqueueResult; 5 | import ru.yoomoney.tech.dbqueue.api.QueueProducer; 6 | import ru.yoomoney.tech.dbqueue.api.QueueShardRouter; 7 | import ru.yoomoney.tech.dbqueue.api.TaskPayloadTransformer; 8 | import ru.yoomoney.tech.dbqueue.config.DatabaseAccessLayer; 9 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 10 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 11 | 12 | import javax.annotation.Nonnull; 13 | import java.util.Objects; 14 | 15 | /** 16 | * Wrapper for queue producer wrapper with sharding support. 17 | * 18 | * @param The type of the payload in the task 19 | * @param The type of the database access layer 20 | * @author Oleg Kandaurov 21 | * @since 11.06.2021 22 | */ 23 | public class ShardingQueueProducer 24 | implements QueueProducer { 25 | 26 | @Nonnull 27 | private final QueueShardRouter queueShardRouter; 28 | @Nonnull 29 | private final TaskPayloadTransformer payloadTransformer; 30 | @Nonnull 31 | private final QueueConfig queueConfig; 32 | 33 | /** 34 | * Constructor 35 | * 36 | * @param queueConfig Configuration of the queue 37 | * @param payloadTransformer Transformer of a payload data 38 | * @param queueShardRouter Dispatcher for sharding support 39 | */ 40 | public ShardingQueueProducer(@Nonnull QueueConfig queueConfig, 41 | @Nonnull TaskPayloadTransformer payloadTransformer, 42 | @Nonnull QueueShardRouter queueShardRouter) { 43 | this.queueShardRouter = Objects.requireNonNull(queueShardRouter); 44 | this.payloadTransformer = Objects.requireNonNull(payloadTransformer); 45 | this.queueConfig = Objects.requireNonNull(queueConfig); 46 | } 47 | 48 | @Override 49 | public EnqueueResult enqueue(@Nonnull EnqueueParams enqueueParams) { 50 | QueueShard queueShard = queueShardRouter.resolveShard(enqueueParams); 51 | EnqueueParams rawEnqueueParams = new EnqueueParams() 52 | .withPayload(payloadTransformer.fromObject(enqueueParams.getPayload())) 53 | .withExecutionDelay(enqueueParams.getExecutionDelay()) 54 | .withExtData(enqueueParams.getExtData()); 55 | Long enqueueId = queueShard.getDatabaseAccessLayer().transact(() -> 56 | queueShard.getDatabaseAccessLayer().getQueueDao().enqueue(queueConfig.getLocation(), rawEnqueueParams)); 57 | return EnqueueResult.builder() 58 | .withShardId(queueShard.getShardId()) 59 | .withEnqueueId(enqueueId) 60 | .build(); 61 | } 62 | 63 | @Nonnull 64 | @Override 65 | public TaskPayloadTransformer getPayloadTransformer() { 66 | return payloadTransformer; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/api/impl/SingleQueueShardRouter.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api.impl; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.EnqueueParams; 4 | import ru.yoomoney.tech.dbqueue.api.QueueShardRouter; 5 | import ru.yoomoney.tech.dbqueue.config.DatabaseAccessLayer; 6 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 7 | 8 | import javax.annotation.Nonnull; 9 | import java.util.Objects; 10 | 11 | /** 12 | * Shard router without sharding. Might be helpful if you have single database instance. 13 | * 14 | * @param The type of the payload in the task 15 | * @param The type of the database access layer 16 | * @author Oleg Kandaurov 17 | * @since 11.06.2021 18 | */ 19 | public class SingleQueueShardRouter 20 | implements QueueShardRouter { 21 | 22 | @Nonnull 23 | private final QueueShard queueShard; 24 | 25 | /** 26 | * Constructor 27 | * 28 | * @param queueShard queue shard 29 | */ 30 | public SingleQueueShardRouter(@Nonnull QueueShard queueShard) { 31 | this.queueShard = Objects.requireNonNull(queueShard, "queueShard must not be null"); 32 | } 33 | 34 | @Override 35 | public QueueShard resolveShard(EnqueueParams enqueueParams) { 36 | return queueShard; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/DatabaseAccessLayer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import ru.yoomoney.tech.dbqueue.dao.QueueDao; 4 | import ru.yoomoney.tech.dbqueue.dao.QueuePickTaskDao; 5 | import ru.yoomoney.tech.dbqueue.settings.FailureSettings; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 7 | 8 | import javax.annotation.Nonnull; 9 | import java.util.function.Supplier; 10 | 11 | /** 12 | * Interface for interacting with database 13 | * 14 | * @author Oleg Kandaurov 15 | * @since 22.04.2021 16 | */ 17 | public interface DatabaseAccessLayer { 18 | 19 | /** 20 | * Get an instance of database-specific DAO based on database type and table schema. 21 | * 22 | * @return database-specific DAO instance. 23 | */ 24 | @Nonnull 25 | QueueDao getQueueDao(); 26 | 27 | /** 28 | * Create an instance of database-specific DAO based on database type and table schema. 29 | * 30 | * @param queueLocation queue location 31 | * @param failureSettings settings for handling failures 32 | * @return database-specific DAO instance. 33 | */ 34 | @Nonnull 35 | QueuePickTaskDao createQueuePickTaskDao(@Nonnull QueueLocation queueLocation, 36 | @Nonnull FailureSettings failureSettings); 37 | 38 | /** 39 | * Perform an operation in transaction 40 | * 41 | * @param result type 42 | * @param supplier operation 43 | * @return result of operation 44 | */ 45 | ResultT transact(@Nonnull Supplier supplier); 46 | 47 | /** 48 | * Perform an operation in transaction 49 | * 50 | * @param runnable operation 51 | */ 52 | void transact(@Nonnull Runnable runnable); 53 | 54 | /** 55 | * Get database type for that database. 56 | * 57 | * @return Database type. 58 | */ 59 | @Nonnull 60 | DatabaseDialect getDatabaseDialect(); 61 | 62 | /** 63 | * Get queue table schema for that database. 64 | * 65 | * @return Queue table schema. 66 | */ 67 | @Nonnull 68 | QueueTableSchema getQueueTableSchema(); 69 | 70 | } 71 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/DatabaseDialect.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 4 | 5 | /** 6 | * Supported database type (dialect) 7 | * 8 | * @author Oleg Kandaurov 9 | * @since 06.10.2019 10 | */ 11 | public enum DatabaseDialect { 12 | /** 13 | * PostgreSQL (version equals or higher than 9.5). 14 | */ 15 | POSTGRESQL, 16 | /** 17 | * Microsoft SQL Server 18 | */ 19 | MSSQL, 20 | /** 21 | * Oracle 11g 22 | *

23 | * This version doesn't have automatically incremented primary keys, 24 | * so you must specify sequence name in 25 | * {@link QueueLocation.Builder#withIdSequence(String)} 26 | */ 27 | ORACLE_11G, 28 | 29 | /** 30 | * H2 in-memory database 31 | */ 32 | H2 33 | } 34 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/QueueShard.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | import static java.util.Objects.requireNonNull; 6 | 7 | /** 8 | * Properties for connection to a database shard. 9 | * 10 | * @param type of database access layer. 11 | * @author Oleg Kandaurov 12 | * @since 13.08.2018 13 | */ 14 | public class QueueShard { 15 | 16 | @Nonnull 17 | private final QueueShardId shardId; 18 | @Nonnull 19 | private final DatabaseAccessLayerT databaseAccessLayer; 20 | 21 | /** 22 | * Constructor 23 | * 24 | * @param shardId Shard identifier. 25 | * @param databaseAccessLayer database access layer. 26 | */ 27 | public QueueShard(@Nonnull QueueShardId shardId, 28 | @Nonnull DatabaseAccessLayerT databaseAccessLayer) { 29 | this.shardId = requireNonNull(shardId); 30 | this.databaseAccessLayer = requireNonNull(databaseAccessLayer); 31 | } 32 | 33 | /** 34 | * Get shard identifier. 35 | * 36 | * @return Shard identifier. 37 | */ 38 | @Nonnull 39 | public QueueShardId getShardId() { 40 | return shardId; 41 | } 42 | 43 | /** 44 | * Get database access layer. 45 | * 46 | * @return database access layer. 47 | */ 48 | @Nonnull 49 | public DatabaseAccessLayerT getDatabaseAccessLayer() { 50 | return databaseAccessLayer; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/QueueShardId.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.util.Objects; 5 | 6 | /** 7 | * Storage for shard information. 8 | * 9 | * @author Oleg Kandaurov 10 | * @since 30.07.2017 11 | */ 12 | public final class QueueShardId { 13 | 14 | @Nonnull 15 | private final String id; 16 | 17 | /** 18 | * Constructor 19 | * 20 | * @param id Shard identifier. 21 | */ 22 | public QueueShardId(@Nonnull String id) { 23 | this.id = Objects.requireNonNull(id); 24 | } 25 | 26 | /** 27 | * Get shard identifier. 28 | * 29 | * @return Shard identifier. 30 | */ 31 | @Nonnull 32 | public String asString() { 33 | return id; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object obj) { 38 | if (this == obj) { 39 | return true; 40 | } 41 | if (obj == null || getClass() != obj.getClass()) { 42 | return false; 43 | } 44 | QueueShardId shardId = (QueueShardId) obj; 45 | return Objects.equals(id, shardId.id); 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(id); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return id; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/QueueThreadFactory.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 7 | 8 | import javax.annotation.Nonnull; 9 | import java.util.Objects; 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.ThreadFactory; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | import java.util.concurrent.atomic.AtomicLong; 14 | 15 | /** 16 | * Thread factory for tasks execution pool. 17 | * 18 | * @author Oleg Kandaurov 19 | * @since 02.10.2019 20 | */ 21 | class QueueThreadFactory implements ThreadFactory { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(QueueThreadFactory.class); 24 | 25 | private static final String THREAD_FACTORY_NAME = "queue-"; 26 | private static final AtomicLong threadNumber = new AtomicLong(0); 27 | private final Thread.UncaughtExceptionHandler exceptionHandler = 28 | new QueueUncaughtExceptionHandler(); 29 | @Nonnull 30 | private final QueueLocation location; 31 | @Nonnull 32 | private final QueueShardId shardId; 33 | 34 | @SuppressFBWarnings("STT_TOSTRING_STORED_IN_FIELD") 35 | QueueThreadFactory(@Nonnull QueueLocation location, @Nonnull QueueShardId shardId) { 36 | this.location = Objects.requireNonNull(location); 37 | this.shardId = Objects.requireNonNull(shardId); 38 | } 39 | 40 | @Override 41 | public Thread newThread(@Nonnull Runnable runnable) { 42 | String threadName = THREAD_FACTORY_NAME + threadNumber.getAndIncrement(); 43 | Thread thread = new QueueThread(Thread.currentThread().getThreadGroup(), runnable, threadName, 44 | 0, location, shardId); 45 | thread.setUncaughtExceptionHandler(exceptionHandler); 46 | return thread; 47 | } 48 | 49 | private static class QueueThread extends Thread { 50 | 51 | @Nonnull 52 | private final QueueLocation location; 53 | @Nonnull 54 | private final QueueShardId shardId; 55 | 56 | public QueueThread(ThreadGroup group, Runnable target, String name, long stackSize, 57 | @Nonnull QueueLocation location, @Nonnull QueueShardId shardId) { 58 | super(group, target, name, stackSize); 59 | this.location = Objects.requireNonNull(location); 60 | this.shardId = Objects.requireNonNull(shardId); 61 | } 62 | 63 | @Override 64 | public void run() { 65 | log.info("starting queue thread: threadName={}, location={}, shardId={}", getName(), location, shardId); 66 | super.run(); 67 | log.info("disposing queue thread: threadName={}, location={}, shardId={}", getName(), location, shardId); 68 | } 69 | } 70 | 71 | private static class QueueUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 72 | 73 | private static final Logger log = LoggerFactory.getLogger(QueueUncaughtExceptionHandler.class); 74 | 75 | @Override 76 | public void uncaughtException(Thread thread, Throwable throwable) { 77 | log.error("detected uncaught exception", throwable); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/TaskLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 4 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 6 | 7 | import javax.annotation.Nonnull; 8 | import javax.annotation.Nullable; 9 | 10 | /** 11 | * Listener for task processing lifecycle. 12 | * 13 | * @author Oleg Kandaurov 14 | * @since 09.07.2017 15 | */ 16 | public interface TaskLifecycleListener { 17 | 18 | /** 19 | * Event of task picking from the queue. 20 | *

21 | * Triggered when there is a task in the queue, which is ready for processing. 22 | *

23 | * Might be useful for monitoring problems with database performance. 24 | * 25 | * @param shardId Shard identifier, which processes the queue. 26 | * @param location Queue location. 27 | * @param taskRecord Raw task data. 28 | * @param pickTaskTime Time spent on picking the task from the queue in millis. 29 | */ 30 | void picked(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord, 31 | long pickTaskTime); 32 | 33 | /** 34 | * The start event of task processing. 35 | *

36 | * Always triggered when task was picked. 37 | *

38 | * Might be useful for updating a logging context. 39 | * 40 | * @param shardId Shard identifier, which processes the queue. 41 | * @param location Queue location. 42 | * @param taskRecord Raw task data. 43 | */ 44 | void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord); 45 | 46 | /** 47 | * Event for completion of client logic when task processing. 48 | *

49 | * Always triggered when task processing has completed successfully. 50 | *

51 | * Might be useful for monitoring successful execution of client logic. 52 | * 53 | * @param shardId Shard identifier, which processes the queue. 54 | * @param location Queue location. 55 | * @param taskRecord Raw task data. 56 | * @param executionResult Result of task processing. 57 | * @param processTaskTime Time spent on task processing in millis, without the time for task picking from the queue. 58 | */ 59 | void executed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord, 60 | @Nonnull TaskExecutionResult executionResult, long processTaskTime); 61 | 62 | /** 63 | * Event for completion the task execution in the queue. 64 | *

65 | * Always triggered when task was picked up for processing. 66 | * Called even after {@link #crashed}. 67 | *

68 | * Might be useful for recovery of initial logging context state. 69 | * 70 | * @param shardId Shard identifier, which processes the queue. 71 | * @param location Queue location. 72 | * @param taskRecord Raw task data. 73 | */ 74 | void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord); 75 | 76 | 77 | /** 78 | * Event for abnormal queue processing. 79 | *

80 | * Triggered when unexpected error occurs during task processing. 81 | *

82 | * Might be useful for tracking and monitoring errors in the system. 83 | * 84 | * @param shardId Shard identifier, which processes the queue. 85 | * @param location Queue location. 86 | * @param taskRecord Raw task data. 87 | * @param exc An error caused the crash. 88 | */ 89 | void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nonnull TaskRecord taskRecord, 90 | @Nullable Exception exc); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/ThreadLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 4 | 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * Listener for task processing thread in the queue. 10 | * 11 | * @author Oleg Kandaurov 12 | * @since 16.07.2017 13 | */ 14 | public interface ThreadLifecycleListener { 15 | 16 | /** 17 | * Start of the task processing in the queue. 18 | *

19 | * Always called. 20 | *

21 | * Might be useful for setting values in the logging context or change thread name. 22 | * 23 | * @param shardId Shard identifier, which processes the queue. 24 | * @param location Queue location. 25 | */ 26 | void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location); 27 | 28 | /** 29 | * Thread was executed and finished processing. 30 | *

31 | * Called when normal end of task processing. 32 | *

33 | * Might be useful for measuring performance of the queue. 34 | * 35 | * @param shardId Shard identifier, which processes the queue. 36 | * @param location Queue location. 37 | * @param taskProcessed Attribute that task was taken and processed, no tasks for processing otherwise. 38 | * @param threadBusyTime Time in millis of the thread was running active before sleep. 39 | */ 40 | void executed(QueueShardId shardId, QueueLocation location, boolean taskProcessed, long threadBusyTime); 41 | 42 | /** 43 | * End of the task processing lifecycle and start of the new one. 44 | *

45 | * Always called, even after {@link #crashed}. 46 | *

47 | * Might be useful for logging context return or move the thread to the initial state. 48 | * 49 | * @param shardId Shard identifier, which processes the queue. 50 | * @param location Queue location. 51 | */ 52 | void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location); 53 | 54 | /** 55 | * Queue failed with fatal error. 56 | *

57 | * Client code cannot trigger that method call, 58 | * this method is called when task picking crashed. 59 | *

60 | * Might be useful for logging and monitoring. 61 | * 62 | * @param shardId Shard identifier, which processes the queue. 63 | * @param location Queue location. 64 | * @param exc An error caused the crash. 65 | */ 66 | void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nullable Throwable exc); 67 | } 68 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/impl/CompositeTaskLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config.impl; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 4 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 5 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 6 | import ru.yoomoney.tech.dbqueue.config.TaskLifecycleListener; 7 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 8 | 9 | import javax.annotation.Nonnull; 10 | import javax.annotation.Nullable; 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Objects; 15 | 16 | /** 17 | * Composite listener. It allows combining several listeners into one. 18 | * 19 | * Listeners for picked and started events are executed in straight order. 20 | * Listeners for executed, finished and crashed events are executed in reverse order. 21 | * 22 | * @author Oleg Kandaurov 23 | * @since 11.06.2021 24 | */ 25 | public class CompositeTaskLifecycleListener implements TaskLifecycleListener { 26 | 27 | @Nonnull 28 | private final List listeners; 29 | @Nonnull 30 | private final List reverseListeners; 31 | 32 | /** 33 | * Constructor 34 | * 35 | * @param listeners task listeners 36 | */ 37 | public CompositeTaskLifecycleListener(@Nonnull List listeners) { 38 | this.listeners = Objects.requireNonNull(listeners, "listeners must not be null"); 39 | this.reverseListeners = new ArrayList<>(listeners); 40 | Collections.reverse(reverseListeners); 41 | } 42 | 43 | @Override 44 | public void picked(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 45 | @Nonnull TaskRecord taskRecord, long pickTaskTime) { 46 | listeners.forEach(l -> l.picked(shardId, location, taskRecord, pickTaskTime)); 47 | } 48 | 49 | @Override 50 | public void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 51 | @Nonnull TaskRecord taskRecord) { 52 | listeners.forEach(l -> l.started(shardId, location, taskRecord)); 53 | } 54 | 55 | @Override 56 | public void executed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 57 | @Nonnull TaskRecord taskRecord, 58 | @Nonnull TaskExecutionResult executionResult, long processTaskTime) { 59 | reverseListeners.forEach(l -> l.executed(shardId, location, taskRecord, executionResult, processTaskTime)); 60 | } 61 | 62 | @Override 63 | public void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 64 | @Nonnull TaskRecord taskRecord) { 65 | reverseListeners.forEach(l -> l.finished(shardId, location, taskRecord)); 66 | } 67 | 68 | @Override 69 | public void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 70 | @Nonnull TaskRecord taskRecord, 71 | @Nullable Exception exc) { 72 | reverseListeners.forEach(l -> l.crashed(shardId, location, taskRecord, exc)); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/impl/CompositeThreadLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config.impl; 2 | 3 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 4 | import ru.yoomoney.tech.dbqueue.config.ThreadLifecycleListener; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 6 | 7 | import javax.annotation.Nonnull; 8 | import javax.annotation.Nullable; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Objects; 13 | 14 | /** 15 | * Composite listener. It allows combining several listeners into one. 16 | * 17 | * Listeners for started events is executed in straight order. 18 | * Listeners for executed, finished and crashed events are executed in reverse order. 19 | * 20 | * @author Oleg Kandaurov 21 | * @since 11.06.2021 22 | */ 23 | public class CompositeThreadLifecycleListener implements ThreadLifecycleListener { 24 | 25 | @Nonnull 26 | private final List listeners; 27 | @Nonnull 28 | private final List reverseListeners; 29 | 30 | /** 31 | * Constructor 32 | * 33 | * @param listeners thread listeners 34 | */ 35 | public CompositeThreadLifecycleListener(@Nonnull List listeners) { 36 | this.listeners = Objects.requireNonNull(listeners, "listeners must not be null"); 37 | this.reverseListeners = new ArrayList<>(listeners); 38 | Collections.reverse(reverseListeners); 39 | } 40 | 41 | @Override 42 | public void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location) { 43 | listeners.forEach(l -> l.started(shardId, location)); 44 | } 45 | 46 | @Override 47 | public void executed(QueueShardId shardId, QueueLocation location, boolean taskProcessed, long threadBusyTime) { 48 | reverseListeners.forEach(l -> l.executed(shardId, location, taskProcessed, threadBusyTime)); 49 | } 50 | 51 | @Override 52 | public void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location) { 53 | reverseListeners.forEach(l -> l.finished(shardId, location)); 54 | } 55 | 56 | @Override 57 | public void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, @Nullable Throwable exc) { 58 | reverseListeners.forEach(l -> l.crashed(shardId, location, exc)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/impl/LoggingTaskLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 6 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 7 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 8 | import ru.yoomoney.tech.dbqueue.config.TaskLifecycleListener; 9 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 10 | 11 | import javax.annotation.Nonnull; 12 | import javax.annotation.Nullable; 13 | import java.time.Clock; 14 | import java.time.Duration; 15 | import java.time.ZonedDateTime; 16 | import java.util.Objects; 17 | 18 | /** 19 | * Task listener with logging support 20 | * 21 | * @author Oleg Kandaurov 22 | * @since 11.06.2021 23 | */ 24 | public class LoggingTaskLifecycleListener implements TaskLifecycleListener { 25 | 26 | private static final Logger log = LoggerFactory.getLogger(LoggingTaskLifecycleListener.class); 27 | 28 | private final Clock clock; 29 | 30 | /** 31 | * Constructor 32 | */ 33 | public LoggingTaskLifecycleListener() { 34 | this(Clock.systemDefaultZone()); 35 | } 36 | 37 | /** 38 | * Constructor for testing purpose 39 | * 40 | * @param clock A clock to mock current time 41 | */ 42 | LoggingTaskLifecycleListener(@Nonnull Clock clock) { 43 | this.clock = Objects.requireNonNull(clock); 44 | } 45 | 46 | @Override 47 | public void picked(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 48 | @Nonnull TaskRecord taskRecord, long pickTaskTime) { 49 | } 50 | 51 | @Override 52 | public void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 53 | @Nonnull TaskRecord taskRecord) { 54 | log.info("consuming task: id={}, attempt={}", taskRecord.getId(), taskRecord.getAttemptsCount()); 55 | } 56 | 57 | @Override 58 | public void executed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 59 | @Nonnull TaskRecord taskRecord, 60 | @Nonnull TaskExecutionResult executionResult, long processTaskTime) { 61 | switch (executionResult.getActionType()) { 62 | case FINISH: 63 | Duration inQueueTime = Duration.between(taskRecord.getCreatedAt(), ZonedDateTime.now(clock)); 64 | log.info("task finished: id={}, in-queue={}, time={}", taskRecord.getId(), inQueueTime, processTaskTime); 65 | break; 66 | case REENQUEUE: 67 | log.info("task reenqueued: id={}, delay={}, time={}", taskRecord.getId(), 68 | executionResult.getExecutionDelay().orElse(null), processTaskTime); 69 | break; 70 | case FAIL: 71 | log.info("task failed: id={}, time={}", taskRecord.getId(), processTaskTime); 72 | break; 73 | default: 74 | log.warn("unknown action type: type={}", executionResult.getActionType()); 75 | break; 76 | } 77 | } 78 | 79 | @Override 80 | public void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 81 | @Nonnull TaskRecord taskRecord) { 82 | } 83 | 84 | @Override 85 | public void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 86 | @Nonnull TaskRecord taskRecord, 87 | @Nullable Exception exc) { 88 | log.error("error while processing task: task={}", taskRecord, exc); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/impl/LoggingThreadLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 6 | import ru.yoomoney.tech.dbqueue.config.ThreadLifecycleListener; 7 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 8 | 9 | import javax.annotation.Nonnull; 10 | import javax.annotation.Nullable; 11 | 12 | /** 13 | * Thread listener with logging support 14 | * 15 | * @author Oleg Kandaurov 16 | * @since 11.06.2021 17 | */ 18 | public class LoggingThreadLifecycleListener implements ThreadLifecycleListener { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(LoggingThreadLifecycleListener.class); 21 | 22 | @Override 23 | public void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location) { 24 | } 25 | 26 | @Override 27 | public void executed(QueueShardId shardId, QueueLocation location, boolean taskProcessed, long threadBusyTime) { 28 | } 29 | 30 | @Override 31 | public void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location) { 32 | } 33 | 34 | @Override 35 | public void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 36 | @Nullable Throwable exc) { 37 | log.error("fatal error in queue thread: shardId={}, location={}", shardId.asString(), 38 | location, exc); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/impl/NoopTaskLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config.impl; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 4 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 5 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 6 | import ru.yoomoney.tech.dbqueue.config.TaskLifecycleListener; 7 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 8 | 9 | import javax.annotation.Nonnull; 10 | import javax.annotation.Nullable; 11 | 12 | /** 13 | * Empty listener for task processing lifecycle. 14 | * 15 | * @author Oleg Kandaurov 16 | * @since 02.10.2019 17 | */ 18 | public final class NoopTaskLifecycleListener implements TaskLifecycleListener { 19 | 20 | private static final NoopTaskLifecycleListener INSTANCE = new NoopTaskLifecycleListener(); 21 | 22 | @Nonnull 23 | public static NoopTaskLifecycleListener getInstance() { 24 | return INSTANCE; 25 | } 26 | 27 | @Override 28 | public void picked(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 29 | @Nonnull TaskRecord taskRecord, long pickTaskTime) { 30 | 31 | } 32 | 33 | @Override 34 | public void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 35 | @Nonnull TaskRecord taskRecord) { 36 | 37 | } 38 | 39 | @Override 40 | public void executed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 41 | @Nonnull TaskRecord taskRecord, @Nonnull TaskExecutionResult executionResult, long processTaskTime) { 42 | 43 | } 44 | 45 | @Override 46 | public void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 47 | @Nonnull TaskRecord taskRecord) { 48 | 49 | } 50 | 51 | @Override 52 | public void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 53 | @Nonnull TaskRecord taskRecord, @Nullable Exception exc) { 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/config/impl/NoopThreadLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config.impl; 2 | 3 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 4 | import ru.yoomoney.tech.dbqueue.config.ThreadLifecycleListener; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 6 | 7 | import javax.annotation.Nonnull; 8 | import javax.annotation.Nullable; 9 | 10 | /** 11 | * Empty listener for task processing thread in the queue. 12 | * 13 | * @author Oleg Kandaurov 14 | * @since 02.10.2019 15 | */ 16 | public class NoopThreadLifecycleListener implements ThreadLifecycleListener { 17 | 18 | private static final NoopThreadLifecycleListener INSTANCE = new NoopThreadLifecycleListener(); 19 | 20 | @Nonnull 21 | public static NoopThreadLifecycleListener getInstance() { 22 | return INSTANCE; 23 | } 24 | 25 | @Override 26 | public void started(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location) { 27 | 28 | } 29 | 30 | @Override 31 | public void executed(QueueShardId shardId, QueueLocation location, 32 | boolean taskProcessed, long threadBusyTime) { 33 | 34 | } 35 | 36 | @Override 37 | public void finished(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location) { 38 | 39 | } 40 | 41 | @Override 42 | public void crashed(@Nonnull QueueShardId shardId, @Nonnull QueueLocation location, 43 | @Nullable Throwable exc) { 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/dao/QueueDao.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.dao; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.EnqueueParams; 4 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 5 | 6 | import javax.annotation.Nonnull; 7 | import java.time.Duration; 8 | 9 | /** 10 | * Database access object to manage tasks in the queue. 11 | * 12 | * @author Oleg Kandaurov 13 | * @since 06.10.2019 14 | */ 15 | public interface QueueDao { 16 | /** 17 | * Add a new task in the queue for processing. 18 | * 19 | * @param location Queue location. 20 | * @param enqueueParams Parameters of the task 21 | * @return Identifier (sequence id) of new inserted task. 22 | */ 23 | long enqueue(@Nonnull QueueLocation location, @Nonnull EnqueueParams enqueueParams); 24 | 25 | /** 26 | * Remove (delete) task from the queue. 27 | * 28 | * @param location Queue location. 29 | * @param taskId Identifier (sequence id) of the task. 30 | * @return true, if task was deleted from database, false, when task with given id was not found. 31 | */ 32 | boolean deleteTask(@Nonnull QueueLocation location, long taskId); 33 | 34 | /** 35 | * Postpone task processing for given time period (current date and time plus execution delay). 36 | * 37 | * @param location Queue location. 38 | * @param taskId Identifier (sequence id) of the task. 39 | * @param executionDelay Task execution delay. 40 | * @return true, if task was successfully postponed, false, when task was not found. 41 | */ 42 | boolean reenqueue(@Nonnull QueueLocation location, long taskId, @Nonnull Duration executionDelay); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/dao/QueuePickTaskDao.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.dao; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | /** 8 | * Database access object to pick up tasks in the queue. 9 | * 10 | * @author Oleg Kandaurov 11 | * @since 06.10.2019 12 | */ 13 | public interface QueuePickTaskDao { 14 | 15 | /** 16 | * Pick task from a queue 17 | * 18 | * @return task data or null if not found 19 | */ 20 | @Nullable 21 | TaskRecord pickTask(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Не используйте классы и интерфейсы из этого и вложенных пакетов. 3 | * Они могут свободно меняться для внутренних нужд библиотеки. 4 | * 5 | * @author Pavel Raev 6 | * @since 14.08.2019 7 | */ 8 | package ru.yoomoney.tech.dbqueue.internal; -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/processing/MillisTimeProvider.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | /** 4 | * Поставщик текущего времени в миллисекундах. 5 | * 6 | * @author Oleg Kandaurov 7 | * @since 15.07.2017 8 | */ 9 | @FunctionalInterface 10 | public interface MillisTimeProvider { 11 | 12 | /** 13 | * Получить время в миллисекундах. 14 | * 15 | * @return время в миллисекундах 16 | */ 17 | long getMillis(); 18 | 19 | /** 20 | * Поставщик системного времени 21 | */ 22 | class SystemMillisTimeProvider implements MillisTimeProvider { 23 | 24 | @Override 25 | public long getMillis() { 26 | return System.currentTimeMillis(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/processing/QueueProcessingStatus.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | /** 4 | * Тип результата обработки задачи в очереди 5 | * 6 | * @author Oleg Kandaurov 7 | * @since 27.08.2017 8 | */ 9 | public enum QueueProcessingStatus { 10 | 11 | /** 12 | * Задача была обрабатана 13 | */ 14 | PROCESSED, 15 | /** 16 | * Задача не была найдена и обработка не состоялась 17 | */ 18 | SKIPPED 19 | } 20 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/processing/QueueTaskPoller.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 4 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 5 | import ru.yoomoney.tech.dbqueue.config.ThreadLifecycleListener; 6 | import ru.yoomoney.tech.dbqueue.internal.runner.QueueRunner; 7 | import ru.yoomoney.tech.dbqueue.settings.PollSettings; 8 | 9 | import javax.annotation.Nonnull; 10 | 11 | import static java.util.Objects.requireNonNull; 12 | 13 | /** 14 | * Цикл обработки задачи в очереди. 15 | * 16 | * @author Oleg Kandaurov 17 | * @since 09.07.2017 18 | */ 19 | @SuppressWarnings({"rawtypes", "unchecked"}) 20 | public class QueueTaskPoller { 21 | 22 | @Nonnull 23 | private final ThreadLifecycleListener threadLifecycleListener; 24 | @Nonnull 25 | private final MillisTimeProvider millisTimeProvider; 26 | 27 | /** 28 | * Конструктор 29 | * 30 | * @param threadLifecycleListener слушатель событий исполнения очереди 31 | * @param millisTimeProvider поставщик текущего времени 32 | */ 33 | public QueueTaskPoller(@Nonnull ThreadLifecycleListener threadLifecycleListener, 34 | @Nonnull MillisTimeProvider millisTimeProvider) { 35 | this.threadLifecycleListener = requireNonNull(threadLifecycleListener); 36 | this.millisTimeProvider = requireNonNull(millisTimeProvider); 37 | } 38 | 39 | /** 40 | * Запустить цикл обработки задач в очереди 41 | * 42 | * @param queueLoop стратегия выполнения цикла 43 | * @param shardId идентификатор шарда, на котором происходит обработка 44 | * @param queueConsumer выполняемая очередь 45 | * @param queueRunner исполнитель очереди 46 | */ 47 | public void start(@Nonnull QueueLoop queueLoop, 48 | @Nonnull QueueShardId shardId, 49 | @Nonnull QueueConsumer queueConsumer, 50 | @Nonnull QueueRunner queueRunner) { 51 | requireNonNull(shardId); 52 | requireNonNull(queueConsumer); 53 | requireNonNull(queueRunner); 54 | requireNonNull(queueLoop); 55 | queueLoop.doRun(() -> { 56 | PollSettings pollSettings = queueConsumer.getQueueConfig().getSettings().getPollSettings(); 57 | try { 58 | long startTime = millisTimeProvider.getMillis(); 59 | threadLifecycleListener.started(shardId, queueConsumer.getQueueConfig().getLocation()); 60 | QueueProcessingStatus queueProcessingStatus = queueRunner.runQueue(queueConsumer); 61 | threadLifecycleListener.executed(shardId, queueConsumer.getQueueConfig().getLocation(), 62 | queueProcessingStatus != QueueProcessingStatus.SKIPPED, 63 | millisTimeProvider.getMillis() - startTime); 64 | 65 | switch (queueProcessingStatus) { 66 | case SKIPPED: 67 | queueLoop.doWait(pollSettings.getNoTaskTimeout(), 68 | QueueLoop.WaitInterrupt.ALLOW); 69 | return; 70 | case PROCESSED: 71 | queueLoop.doWait(pollSettings.getBetweenTaskTimeout(), 72 | QueueLoop.WaitInterrupt.DENY); 73 | return; 74 | default: 75 | throw new IllegalStateException("unknown task loop result" + queueProcessingStatus); 76 | } 77 | } catch (Throwable e) { 78 | threadLifecycleListener.crashed(shardId, queueConsumer.getQueueConfig().getLocation(), e); 79 | queueLoop.doWait(pollSettings.getFatalCrashTimeout(), 80 | QueueLoop.WaitInterrupt.DENY); 81 | } finally { 82 | threadLifecycleListener.finished(shardId, queueConsumer.getQueueConfig().getLocation()); 83 | } 84 | }); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/processing/TaskPicker.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 4 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 5 | import ru.yoomoney.tech.dbqueue.config.TaskLifecycleListener; 6 | import ru.yoomoney.tech.dbqueue.dao.QueuePickTaskDao; 7 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 8 | 9 | import javax.annotation.Nonnull; 10 | import javax.annotation.Nullable; 11 | 12 | import static java.util.Objects.requireNonNull; 13 | 14 | /** 15 | * Класс, обеспечивающий выборку задачи из очереди 16 | * 17 | * @author Oleg Kandaurov 18 | * @since 19.07.2017 19 | */ 20 | @SuppressWarnings("rawtypes") 21 | public class TaskPicker { 22 | 23 | @Nonnull 24 | private final QueueShard queueShard; 25 | @Nonnull 26 | private final QueueLocation queueLocation; 27 | @Nonnull 28 | private final TaskLifecycleListener taskLifecycleListener; 29 | @Nonnull 30 | private final MillisTimeProvider millisTimeProvider; 31 | 32 | private final QueuePickTaskDao pickTaskDao; 33 | 34 | 35 | /** 36 | * Constructor 37 | * 38 | * @param queueShard shard to bound task picker to 39 | * @param queueLocation queue location 40 | * @param taskLifecycleListener task listener 41 | * @param millisTimeProvider current time provider 42 | * @param pickTaskDao dao for picking up tasks 43 | */ 44 | public TaskPicker(@Nonnull QueueShard queueShard, 45 | @Nonnull QueueLocation queueLocation, 46 | @Nonnull TaskLifecycleListener taskLifecycleListener, 47 | @Nonnull MillisTimeProvider millisTimeProvider, 48 | @Nonnull QueuePickTaskDao pickTaskDao) { 49 | this.queueShard = requireNonNull(queueShard); 50 | this.queueLocation = requireNonNull(queueLocation); 51 | this.taskLifecycleListener = requireNonNull(taskLifecycleListener); 52 | this.millisTimeProvider = requireNonNull(millisTimeProvider); 53 | this.pickTaskDao = requireNonNull(pickTaskDao); 54 | } 55 | 56 | /** 57 | * Выбрать задачу из очереди 58 | * 59 | * @return задача или null если отсутствует 60 | */ 61 | @Nullable 62 | public TaskRecord pickTask() { 63 | long startPickTaskTime = millisTimeProvider.getMillis(); 64 | TaskRecord taskRecord = queueShard.getDatabaseAccessLayer().transact(pickTaskDao::pickTask); 65 | if (taskRecord == null) { 66 | return null; 67 | } 68 | taskLifecycleListener.picked(queueShard.getShardId(), queueLocation, taskRecord, 69 | millisTimeProvider.getMillis() - startPickTaskTime); 70 | return taskRecord; 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/processing/TaskProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 4 | import ru.yoomoney.tech.dbqueue.api.Task; 5 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 6 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 7 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 8 | import ru.yoomoney.tech.dbqueue.config.TaskLifecycleListener; 9 | 10 | import javax.annotation.Nonnull; 11 | 12 | import static java.util.Objects.requireNonNull; 13 | 14 | /** 15 | * Обработчик выбранной задачи 16 | * 17 | * @author Oleg Kandaurov 18 | * @since 19.07.2017 19 | */ 20 | @SuppressWarnings({"rawtypes", "unchecked"}) 21 | public class TaskProcessor { 22 | 23 | @Nonnull 24 | private final QueueShard queueShard; 25 | @Nonnull 26 | private final TaskLifecycleListener taskLifecycleListener; 27 | @Nonnull 28 | private final MillisTimeProvider millisTimeProvider; 29 | @Nonnull 30 | private final TaskResultHandler taskResultHandler; 31 | 32 | /** 33 | * Конструктор 34 | * 35 | * @param queueShard шард на котором происходит выполнение задачи 36 | * @param taskLifecycleListener слушатель жизненного цикла задачи в очереди 37 | * @param millisTimeProvider поставщик текущего времени 38 | * @param taskResultHandler обработчик результата выполнения задачи 39 | */ 40 | public TaskProcessor(@Nonnull QueueShard queueShard, 41 | @Nonnull TaskLifecycleListener taskLifecycleListener, 42 | @Nonnull MillisTimeProvider millisTimeProvider, 43 | @Nonnull TaskResultHandler taskResultHandler) { 44 | this.queueShard = requireNonNull(queueShard); 45 | this.taskLifecycleListener = requireNonNull(taskLifecycleListener); 46 | this.millisTimeProvider = requireNonNull(millisTimeProvider); 47 | this.taskResultHandler = requireNonNull(taskResultHandler); 48 | } 49 | 50 | /** 51 | * Передать выбранную задачу в клиентский код на выполнение и обработать результат 52 | * 53 | * @param queueConsumer очередь 54 | * @param taskRecord запись на обработку 55 | */ 56 | public void processTask(@Nonnull QueueConsumer queueConsumer, @Nonnull TaskRecord taskRecord) { 57 | requireNonNull(queueConsumer); 58 | requireNonNull(taskRecord); 59 | try { 60 | taskLifecycleListener.started(queueShard.getShardId(), queueConsumer.getQueueConfig().getLocation(), 61 | taskRecord); 62 | long processTaskStarted = millisTimeProvider.getMillis(); 63 | Object payload = queueConsumer.getPayloadTransformer().toObject(taskRecord.getPayload()); 64 | Task task = Task.builder(queueShard.getShardId()) 65 | .withCreatedAt(taskRecord.getCreatedAt()) 66 | .withPayload(payload) 67 | .withAttemptsCount(taskRecord.getAttemptsCount()) 68 | .withReenqueueAttemptsCount(taskRecord.getReenqueueAttemptsCount()) 69 | .withTotalAttemptsCount(taskRecord.getTotalAttemptsCount()) 70 | .withExtData(taskRecord.getExtData()) 71 | .build(); 72 | TaskExecutionResult executionResult = queueConsumer.execute(task); 73 | taskLifecycleListener.executed(queueShard.getShardId(), queueConsumer.getQueueConfig().getLocation(), 74 | taskRecord, 75 | executionResult, millisTimeProvider.getMillis() - processTaskStarted); 76 | taskResultHandler.handleResult(taskRecord, executionResult); 77 | } catch (Exception exc) { 78 | taskLifecycleListener.crashed(queueShard.getShardId(), queueConsumer.getQueueConfig().getLocation(), 79 | taskRecord, exc); 80 | } finally { 81 | taskLifecycleListener.finished(queueShard.getShardId(), queueConsumer.getQueueConfig().getLocation(), 82 | taskRecord); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/processing/TaskResultHandler.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 4 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 5 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 7 | import ru.yoomoney.tech.dbqueue.settings.ReenqueueSettings; 8 | 9 | import javax.annotation.Nonnull; 10 | 11 | import static java.util.Objects.requireNonNull; 12 | 13 | /** 14 | * Обработчик результат выполенения задачи 15 | * 16 | * @author Oleg Kandaurov 17 | * @since 04.08.2017 18 | */ 19 | public class TaskResultHandler { 20 | 21 | @Nonnull 22 | private final QueueLocation location; 23 | @Nonnull 24 | private final QueueShard queueShard; 25 | @Nonnull 26 | private ReenqueueRetryStrategy reenqueueRetryStrategy; 27 | 28 | /** 29 | * Конструктор 30 | * 31 | * @param location местоположение очереди 32 | * @param queueShard шард на котором происходит обработка задачи 33 | * @param reenqueueSettings настройки переоткладывания задач 34 | */ 35 | public TaskResultHandler(@Nonnull QueueLocation location, 36 | @Nonnull QueueShard queueShard, 37 | @Nonnull ReenqueueSettings reenqueueSettings) { 38 | this.location = requireNonNull(location); 39 | this.queueShard = requireNonNull(queueShard); 40 | this.reenqueueRetryStrategy = ReenqueueRetryStrategy.Factory.create(reenqueueSettings); 41 | reenqueueSettings.registerObserver((oldValue, newValue) -> 42 | reenqueueRetryStrategy = ReenqueueRetryStrategy.Factory.create(newValue)); 43 | } 44 | 45 | /** 46 | * Обработать результат выполнения задачи 47 | * 48 | * @param taskRecord обработанная задача 49 | * @param executionResult результат обработки 50 | */ 51 | public void handleResult(@Nonnull TaskRecord taskRecord, @Nonnull TaskExecutionResult executionResult) { 52 | requireNonNull(taskRecord); 53 | requireNonNull(executionResult); 54 | 55 | switch (executionResult.getActionType()) { 56 | case FINISH: 57 | queueShard.getDatabaseAccessLayer().transact(() -> queueShard.getDatabaseAccessLayer().getQueueDao() 58 | .deleteTask(location, taskRecord.getId())); 59 | return; 60 | 61 | case REENQUEUE: 62 | queueShard.getDatabaseAccessLayer().transact(() -> queueShard.getDatabaseAccessLayer().getQueueDao() 63 | .reenqueue(location, taskRecord.getId(), 64 | executionResult.getExecutionDelay().orElseGet( 65 | () -> reenqueueRetryStrategy.calculateDelay(taskRecord)))); 66 | return; 67 | case FAIL: 68 | return; 69 | 70 | default: 71 | throw new IllegalStateException("unknown action type: " + executionResult.getActionType()); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/processing/TimeLimiter.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.time.Duration; 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * Класс, для ограничения времени нескольких действий в заданный таймаут 10 | * 11 | * @author Oleg Kandaurov 12 | * @since 18.10.2019 13 | */ 14 | public class TimeLimiter { 15 | @Nonnull 16 | private final MillisTimeProvider millisTimeProvider; 17 | private Duration remainingTimeout; 18 | private Duration elapsedTime = Duration.ZERO; 19 | 20 | public TimeLimiter(@Nonnull MillisTimeProvider millisTimeProvider, 21 | @Nonnull Duration timeout) { 22 | this.millisTimeProvider = Objects.requireNonNull(millisTimeProvider); 23 | this.remainingTimeout = Objects.requireNonNull(timeout); 24 | } 25 | 26 | /** 27 | * Выполнить действие с контролем времени. 28 | * Если заданный таймаут истёк, то действие не будет выполняться. 29 | * 30 | * @param consumer вызываемое действие, с передачей в аргументы оставшегося времени выполнения 31 | */ 32 | public void execute(@Nonnull Consumer consumer) { 33 | Objects.requireNonNull(consumer); 34 | if (remainingTimeout.equals(Duration.ZERO)) { 35 | return; 36 | } 37 | long startTime = millisTimeProvider.getMillis(); 38 | consumer.accept(remainingTimeout); 39 | elapsedTime = elapsedTime.plus(Duration.ofMillis(millisTimeProvider.getMillis() - startTime)); 40 | if (remainingTimeout.compareTo(elapsedTime) <= 0) { 41 | remainingTimeout = Duration.ZERO; 42 | } else { 43 | remainingTimeout = remainingTimeout.minus(elapsedTime); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/runner/BaseQueueRunner.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.runner; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 4 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 5 | import ru.yoomoney.tech.dbqueue.internal.processing.QueueProcessingStatus; 6 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskPicker; 7 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskProcessor; 8 | 9 | import javax.annotation.Nonnull; 10 | import java.util.Objects; 11 | import java.util.concurrent.Executor; 12 | 13 | /** 14 | * Базовая реализация обработчика задач очереди 15 | * 16 | * @author Oleg Kandaurov 17 | * @since 27.08.2017 18 | */ 19 | public class BaseQueueRunner implements QueueRunner { 20 | 21 | @Nonnull 22 | private final TaskPicker taskPicker; 23 | @Nonnull 24 | private final TaskProcessor taskProcessor; 25 | @Nonnull 26 | private final Executor executor; 27 | 28 | /** 29 | * Конструктор 30 | * 31 | * @param taskPicker выборщик задачи 32 | * @param taskProcessor обработчик задачи 33 | * @param executor исполнитель задачи 34 | */ 35 | BaseQueueRunner(@Nonnull TaskPicker taskPicker, 36 | @Nonnull TaskProcessor taskProcessor, 37 | @Nonnull Executor executor) { 38 | this.taskPicker = Objects.requireNonNull(taskPicker); 39 | this.taskProcessor = Objects.requireNonNull(taskProcessor); 40 | this.executor = Objects.requireNonNull(executor); 41 | } 42 | 43 | @Override 44 | @Nonnull 45 | public QueueProcessingStatus runQueue(@Nonnull QueueConsumer queueConsumer) { 46 | TaskRecord taskRecord = taskPicker.pickTask(); 47 | if (taskRecord == null) { 48 | return QueueProcessingStatus.SKIPPED; 49 | } 50 | executor.execute(() -> taskProcessor.processTask(queueConsumer, taskRecord)); 51 | return QueueProcessingStatus.PROCESSED; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/runner/QueueRunnerInExternalExecutor.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.runner; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 4 | import ru.yoomoney.tech.dbqueue.internal.processing.QueueProcessingStatus; 5 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskPicker; 6 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskProcessor; 7 | import ru.yoomoney.tech.dbqueue.settings.ProcessingMode; 8 | 9 | import javax.annotation.Nonnull; 10 | import java.util.concurrent.Executor; 11 | 12 | /** 13 | * Исполнитель задач очереди в режиме 14 | * {@link ProcessingMode#USE_EXTERNAL_EXECUTOR} 15 | * 16 | * @author Oleg Kandaurov 17 | * @since 16.07.2017 18 | */ 19 | @SuppressWarnings({"rawtypes", "unchecked"}) 20 | class QueueRunnerInExternalExecutor implements QueueRunner { 21 | 22 | private final BaseQueueRunner baseQueueRunner; 23 | 24 | /** 25 | * Конструктор 26 | * 27 | * @param taskPicker выборщик задачи 28 | * @param taskProcessor обработчик задачи 29 | * @param externalExecutor исполнитель задачи 30 | */ 31 | QueueRunnerInExternalExecutor(@Nonnull TaskPicker taskPicker, 32 | @Nonnull TaskProcessor taskProcessor, 33 | @Nonnull Executor externalExecutor) { 34 | baseQueueRunner = new BaseQueueRunner(taskPicker, taskProcessor, externalExecutor); 35 | } 36 | 37 | @Override 38 | @Nonnull 39 | public QueueProcessingStatus runQueue(@Nonnull QueueConsumer queueConsumer) { 40 | return baseQueueRunner.runQueue(queueConsumer); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/runner/QueueRunnerInSeparateTransactions.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.runner; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 4 | import ru.yoomoney.tech.dbqueue.internal.processing.QueueProcessingStatus; 5 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskPicker; 6 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskProcessor; 7 | import ru.yoomoney.tech.dbqueue.settings.ProcessingMode; 8 | 9 | import javax.annotation.Nonnull; 10 | 11 | /** 12 | * Исполнитель задач очереди в режиме 13 | * {@link ProcessingMode#SEPARATE_TRANSACTIONS} 14 | * 15 | * @author Oleg Kandaurov 16 | * @since 16.07.2017 17 | */ 18 | @SuppressWarnings({"rawtypes", "unchecked"}) 19 | class QueueRunnerInSeparateTransactions implements QueueRunner { 20 | 21 | private final BaseQueueRunner baseQueueRunner; 22 | 23 | /** 24 | * Конструктор 25 | * 26 | * @param taskPicker выборщик задачи 27 | * @param taskProcessor обработчик задачи 28 | */ 29 | QueueRunnerInSeparateTransactions(@Nonnull TaskPicker taskPicker, 30 | @Nonnull TaskProcessor taskProcessor) { 31 | baseQueueRunner = new BaseQueueRunner(taskPicker, taskProcessor, Runnable::run); 32 | } 33 | 34 | @Override 35 | @Nonnull 36 | public QueueProcessingStatus runQueue(@Nonnull QueueConsumer queueConsumer) { 37 | return baseQueueRunner.runQueue(queueConsumer); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/internal/runner/QueueRunnerInTransaction.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.runner; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 5 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 6 | import ru.yoomoney.tech.dbqueue.internal.processing.QueueProcessingStatus; 7 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskPicker; 8 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskProcessor; 9 | import ru.yoomoney.tech.dbqueue.settings.ProcessingMode; 10 | 11 | import javax.annotation.Nonnull; 12 | 13 | import static java.util.Objects.requireNonNull; 14 | 15 | /** 16 | * Исполнитель задач очереди в режиме 17 | * {@link ProcessingMode#WRAP_IN_TRANSACTION} 18 | * 19 | * @author Oleg Kandaurov 20 | * @since 16.07.2017 21 | */ 22 | @SuppressWarnings("rawtypes") 23 | class QueueRunnerInTransaction implements QueueRunner { 24 | 25 | @Nonnull 26 | private final QueueShard queueShard; 27 | private final BaseQueueRunner baseQueueRunner; 28 | 29 | /** 30 | * Конструктор 31 | * 32 | * @param taskPicker выборщик задачи 33 | * @param taskProcessor обработчик задачи 34 | * @param queueShard шард на котором обрабатываются задачи 35 | */ 36 | QueueRunnerInTransaction(@Nonnull TaskPicker taskPicker, 37 | @Nonnull TaskProcessor taskProcessor, 38 | @Nonnull QueueShard queueShard) { 39 | this.queueShard = requireNonNull(queueShard); 40 | baseQueueRunner = new BaseQueueRunner(taskPicker, taskProcessor, Runnable::run); 41 | } 42 | 43 | @Override 44 | @Nonnull 45 | @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") 46 | public QueueProcessingStatus runQueue(@Nonnull QueueConsumer queueConsumer) { 47 | return requireNonNull(queueShard.getDatabaseAccessLayer() 48 | .transact(() -> baseQueueRunner.runQueue(queueConsumer))); 49 | } 50 | } -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/DynamicSetting.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import javax.annotation.Nonnull; 7 | import java.util.Collection; 8 | import java.util.Objects; 9 | import java.util.Optional; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | import java.util.function.BiConsumer; 12 | import java.util.function.BiFunction; 13 | 14 | /** 15 | * Base class for dynamic settings. 16 | *

17 | * Use it when you need track changes in some setting. 18 | * 19 | * @param type of setting 20 | * @author Oleg Kandaurov 21 | * @since 01.10.2021 22 | */ 23 | abstract class DynamicSetting { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(DynamicSetting.class); 26 | 27 | @Nonnull 28 | private final Collection> observers = new CopyOnWriteArrayList<>(); 29 | 30 | /** 31 | * Name of setting 32 | * 33 | * @return name 34 | */ 35 | @Nonnull 36 | protected abstract String getName(); 37 | 38 | /** 39 | * Function evaluates difference between new and old value. 40 | * 1st argument - old value, 2nd argument - new value. 41 | * 42 | * @return difference between two values 43 | */ 44 | @Nonnull 45 | protected abstract BiFunction getDiffEvaluator(); 46 | 47 | /** 48 | * Return typed reference of current object 49 | * 50 | * @return current object 51 | */ 52 | @Nonnull 53 | protected abstract T getThis(); 54 | 55 | /** 56 | * Copy fields of new object to current object. 57 | * 58 | * @param newValue new value 59 | */ 60 | protected abstract void copyFields(@Nonnull T newValue); 61 | 62 | /** 63 | * Sets new value for current setting. 64 | * Notifies observer when property is changed. 65 | * 66 | * @param newValue new value for setting 67 | * @return diff between old value and new value. Returns empty object when no changes detected. 68 | * @see DynamicSetting#registerObserver(BiConsumer) 69 | */ 70 | public final Optional setValue(@Nonnull T newValue) { 71 | T oldValue = getThis(); 72 | try { 73 | Objects.requireNonNull(newValue, getName() + " must not be null"); 74 | if (newValue.equals(oldValue)) { 75 | return Optional.empty(); 76 | } 77 | observers.forEach(observer -> observer.accept(oldValue, newValue)); 78 | String diff = getDiffEvaluator().apply(oldValue, newValue); 79 | copyFields(newValue); 80 | return Optional.of(diff); 81 | } catch (RuntimeException exc) { 82 | log.error("Cannot apply new setting: name={}, oldValue={}, newValue={}", 83 | getName(), oldValue, newValue, exc); 84 | return Optional.empty(); 85 | } 86 | } 87 | 88 | /** 89 | * Register observer to track setting changes. 90 | * 91 | * @param observer consumer which will be notified on property change. 92 | * 1st argument - old value, 2nd argument - new value 93 | */ 94 | public final void registerObserver(BiConsumer observer) { 95 | observers.add(observer); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/FailRetryType.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | /** 4 | * Strategy type for the task deferring in case of retry. 5 | * 6 | * @author Oleg Kandaurov 7 | * @since 10.07.2017 8 | */ 9 | public enum FailRetryType { 10 | 11 | /** 12 | * The task is deferred exponentially relative to the interval 13 | * {@link FailureSettings#getRetryInterval()} 14 | * The denominator of the progression equals 2. 15 | * First 6 terms: 1 2 4 8 16 32 16 | */ 17 | GEOMETRIC_BACKOFF, 18 | /** 19 | * The task is deferred by an arithmetic progression relative to the interval 20 | * {@link FailureSettings#getRetryInterval()}. 21 | * The difference of progression equals 2. 22 | * First 6 terms: 1 3 5 7 9 11 23 | */ 24 | ARITHMETIC_BACKOFF, 25 | /** 26 | * The task is deferred with fixed delay. 27 | *

28 | * Fixed delay value is set through 29 | * {@link FailureSettings#getRetryInterval()} 30 | */ 31 | LINEAR_BACKOFF 32 | } 33 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/FailureSettingsParser.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.time.Duration; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | import java.util.function.Supplier; 10 | 11 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_RETRY_INTERVAL; 12 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_RETRY_TYPE; 13 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.VALUE_TASK_RETRY_TYPE_ARITHMETIC; 14 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.VALUE_TASK_RETRY_TYPE_GEOMETRIC; 15 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.VALUE_TASK_RETRY_TYPE_LINEAR; 16 | 17 | /** 18 | * Parser for {@link FailureSettings} 19 | * 20 | * @author Oleg Kandaurov 21 | * @since 01.10.2021 22 | */ 23 | class FailureSettingsParser { 24 | 25 | private final Supplier defaultSettings; 26 | private final List errorMessages; 27 | 28 | /** 29 | * Constructor 30 | * 31 | * @param defaultSettings default settings 32 | * @param errorMessages list of error messages 33 | */ 34 | FailureSettingsParser(@Nonnull Supplier defaultSettings, 35 | @Nonnull List errorMessages) { 36 | this.defaultSettings = Objects.requireNonNull(defaultSettings, "defaultSettings"); 37 | this.errorMessages = Objects.requireNonNull(errorMessages, "errorMessages"); 38 | } 39 | 40 | /** 41 | * Parse settings 42 | * 43 | * @param queueId raw queue identifier 44 | * @param settings raw settings 45 | * @return settings or empty object in case of failure 46 | */ 47 | Optional parseSettings(@Nonnull String queueId, @Nonnull Map settings) { 48 | Objects.requireNonNull(queueId, "queueId"); 49 | Objects.requireNonNull(settings, "settings"); 50 | try { 51 | FailureSettings.Builder failureSettings = defaultSettings.get(); 52 | settings.forEach((key, value) -> fillSettings(failureSettings, key, value)); 53 | return Optional.of(failureSettings.build()); 54 | } catch (RuntimeException exc) { 55 | errorMessages.add(String.format("cannot build failure settings: queueId=%s, msg=%s", queueId, exc.getMessage())); 56 | return Optional.empty(); 57 | } 58 | } 59 | 60 | private void fillSettings(FailureSettings.Builder failureSettings, String name, String value) { 61 | try { 62 | switch (name) { 63 | case SETTING_RETRY_TYPE: 64 | failureSettings.withRetryType(parseRetryType(value)); 65 | return; 66 | case SETTING_RETRY_INTERVAL: 67 | failureSettings.withRetryInterval(Duration.parse(value)); 68 | return; 69 | default: 70 | return; 71 | 72 | } 73 | } catch (RuntimeException exc) { 74 | errorMessages.add(String.format("cannot parse setting: name=%s, value=%s, exception=%s", name, value, 75 | exc.getClass().getSimpleName() + '(' + exc.getMessage() + ')')); 76 | } 77 | } 78 | 79 | private static FailRetryType parseRetryType(String name) { 80 | switch (name) { 81 | case VALUE_TASK_RETRY_TYPE_GEOMETRIC: 82 | return FailRetryType.GEOMETRIC_BACKOFF; 83 | case VALUE_TASK_RETRY_TYPE_ARITHMETIC: 84 | return FailRetryType.ARITHMETIC_BACKOFF; 85 | case VALUE_TASK_RETRY_TYPE_LINEAR: 86 | return FailRetryType.LINEAR_BACKOFF; 87 | default: 88 | throw new IllegalArgumentException(String.format("unknown retry type: name=%s", name)); 89 | } 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/PollSettingsParser.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.time.Duration; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | import java.util.function.Supplier; 10 | 11 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_BETWEEN_TASK_TIMEOUT; 12 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_FATAL_CRASH_TIMEOUT; 13 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_NO_TASK_TIMEOUT; 14 | 15 | /** 16 | * Parser for {@link PollSettings} 17 | * 18 | * @author Oleg Kandaurov 19 | * @since 01.10.2021 20 | */ 21 | class PollSettingsParser { 22 | 23 | private final Supplier defaultSettings; 24 | private final List errorMessages; 25 | 26 | /** 27 | * Constructor 28 | * 29 | * @param defaultSettings default settings 30 | * @param errorMessages list of error messages 31 | */ 32 | PollSettingsParser(Supplier defaultSettings, List errorMessages) { 33 | this.defaultSettings = defaultSettings; 34 | this.errorMessages = errorMessages; 35 | } 36 | 37 | /** 38 | * Parse settings 39 | * 40 | * @param queueId raw queue identifier 41 | * @param settings raw settings 42 | * @return settings or empty object in case of failure 43 | */ 44 | Optional parseSettings(@Nonnull String queueId, @Nonnull Map settings) { 45 | Objects.requireNonNull(queueId, "queueId"); 46 | Objects.requireNonNull(settings, "settings"); 47 | try { 48 | PollSettings.Builder pollSettings = defaultSettings.get(); 49 | settings.forEach((key, value) -> fillSettings(pollSettings, key, value)); 50 | return Optional.of(pollSettings.build()); 51 | } catch (RuntimeException exc) { 52 | errorMessages.add(String.format("cannot build poll settings: queueId=%s, msg=%s", queueId, exc.getMessage())); 53 | return Optional.empty(); 54 | } 55 | } 56 | 57 | private void fillSettings(PollSettings.Builder pollSettings, String name, String value) { 58 | try { 59 | switch (name) { 60 | case SETTING_NO_TASK_TIMEOUT: 61 | pollSettings.withNoTaskTimeout(Duration.parse(value)); 62 | return; 63 | case SETTING_BETWEEN_TASK_TIMEOUT: 64 | pollSettings.withBetweenTaskTimeout(Duration.parse(value)); 65 | return; 66 | case SETTING_FATAL_CRASH_TIMEOUT: 67 | pollSettings.withFatalCrashTimeout(Duration.parse(value)); 68 | return; 69 | default: 70 | return; 71 | 72 | } 73 | } catch (RuntimeException exc) { 74 | errorMessages.add(String.format("cannot parse setting: name=%s, value=%s, exception=%s", name, value, 75 | exc.getClass().getSimpleName() + '(' + exc.getMessage() + ')')); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/ProcessingMode.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 4 | 5 | /** 6 | * Strategy for task processing in the queue. 7 | * 8 | * @author Oleg Kandaurov 9 | * @since 16.07.2017 10 | */ 11 | public enum ProcessingMode { 12 | /** 13 | * Task will be processed at least once. 14 | * Each call to database will be done in separate transaction. 15 | *

16 | * Should be used when there are external calls (HTTP etc.) in task processor. 17 | * However, external call must be idempotent. 18 | * In that mode the task processor might execute the task again 19 | * if after the successful task processing on client side it will be impossible 20 | * to delete the task from the queue. 21 | */ 22 | SEPARATE_TRANSACTIONS, 23 | /** 24 | * Task processing wrapped into separate database transaction. 25 | * Task will be executed exactly once when all requirements met. 26 | *

27 | * Should be used only when there are no external calls in task processor, 28 | * and the processor is only call the same database where the tasks are stored. 29 | * When all requirements are met, there is a guarantee that the task will be processed exactly once. 30 | * If there are external calls during task processing, 31 | * the database transaction might be kept open for a long period, 32 | * and the transaction pool will be exhausted. 33 | */ 34 | WRAP_IN_TRANSACTION, 35 | 36 | /** 37 | * Task will be processed at least ones, asynchronously in given executor 38 | * {@link QueueConsumer#getExecutor()}. 39 | * Each call to database will be performed in separate transaction. 40 | *

41 | * That mode requires an additional configuration and external executor management. 42 | * The benefit of this mode is a higher throughput 43 | * and capability to set upper limit for task processing speed. 44 | * This is achieved by the fact that the queue threads 45 | * are only picking tasks from the database, 46 | * whilst subsequent task processing is carried out in separate executor. 47 | *

48 | * This mode should be used when the queue performs long-running operations. 49 | * If this mode will not be used, then the problem might be solved 50 | * with increasing the number of queue processing threads, 51 | * although this also will lead to the increasing database idle polls. 52 | */ 53 | USE_EXTERNAL_EXECUTOR 54 | } 55 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/ProcessingSettingsParser.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | import java.util.function.Supplier; 9 | 10 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_PROCESSING_MODE; 11 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_THREAD_COUNT; 12 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.VALUE_PROCESSING_MODE_SEPARATE_TRANSACTIONS; 13 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.VALUE_PROCESSING_MODE_USE_EXTERNAL_EXECUTOR; 14 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.VALUE_PROCESSING_MODE_WRAP_IN_TRANSACTION; 15 | 16 | /** 17 | * Parser for {@link ProcessingSettings} 18 | * 19 | * @author Oleg Kandaurov 20 | * @since 01.10.2021 21 | */ 22 | public class ProcessingSettingsParser { 23 | 24 | private final Supplier defaultSettings; 25 | private final List errorMessages; 26 | 27 | /** 28 | * Constructor 29 | * 30 | * @param defaultSettings default settings 31 | * @param errorMessages list of error messages 32 | */ 33 | ProcessingSettingsParser(@Nonnull Supplier defaultSettings, 34 | @Nonnull List errorMessages) { 35 | this.defaultSettings = Objects.requireNonNull(defaultSettings, "defaultSettings"); 36 | this.errorMessages = Objects.requireNonNull(errorMessages, "errorMessages"); 37 | } 38 | 39 | /** 40 | * Parse settings 41 | * 42 | * @param queueId raw queue identifier 43 | * @param settings raw settings 44 | * @return settings or empty object in case of failure 45 | */ 46 | Optional parseSettings(@Nonnull String queueId, @Nonnull Map settings) { 47 | Objects.requireNonNull(queueId, "queueId"); 48 | Objects.requireNonNull(settings, "settings"); 49 | try { 50 | ProcessingSettings.Builder processingSettings = defaultSettings.get(); 51 | settings.forEach((key, value) -> fillSettings(processingSettings, key, value)); 52 | return Optional.of(processingSettings.build()); 53 | } catch (RuntimeException exc) { 54 | errorMessages.add(String.format("cannot build processing settings: queueId=%s, msg=%s", queueId, exc.getMessage())); 55 | return Optional.empty(); 56 | } 57 | } 58 | 59 | private void fillSettings(ProcessingSettings.Builder processingSettings, String name, String value) { 60 | try { 61 | switch (name) { 62 | case SETTING_THREAD_COUNT: 63 | processingSettings.withThreadCount(Integer.valueOf(value)); 64 | return; 65 | case SETTING_PROCESSING_MODE: 66 | processingSettings.withProcessingMode(parseProcessingMode(value)); 67 | return; 68 | default: 69 | } 70 | } catch (RuntimeException exc) { 71 | errorMessages.add(String.format("cannot parse setting: name=%s, value=%s, exception=%s", name, value, 72 | exc.getClass().getSimpleName() + '(' + exc.getMessage() + ')')); 73 | } 74 | } 75 | 76 | private static ProcessingMode parseProcessingMode(String name) { 77 | switch (name) { 78 | case VALUE_PROCESSING_MODE_SEPARATE_TRANSACTIONS: 79 | return ProcessingMode.SEPARATE_TRANSACTIONS; 80 | case VALUE_PROCESSING_MODE_WRAP_IN_TRANSACTION: 81 | return ProcessingMode.WRAP_IN_TRANSACTION; 82 | case VALUE_PROCESSING_MODE_USE_EXTERNAL_EXECUTOR: 83 | return ProcessingMode.USE_EXTERNAL_EXECUTOR; 84 | default: 85 | throw new IllegalArgumentException(String.format("unknown processing mode: name=%s", name)); 86 | } 87 | } 88 | 89 | 90 | } 91 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/QueueConfig.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.util.Objects; 5 | 6 | /** 7 | * Queue configuration with database table location and task processing settings. 8 | * 9 | * @author Oleg Kandaurov 10 | * @since 09.07.2017 11 | */ 12 | public final class QueueConfig { 13 | 14 | @Nonnull 15 | private final QueueLocation location; 16 | @Nonnull 17 | private final QueueSettings settings; 18 | 19 | /** 20 | * Constructor for queue configuration 21 | * 22 | * @param location Queue location 23 | * @param settings Queue settings 24 | */ 25 | public QueueConfig(@Nonnull QueueLocation location, 26 | @Nonnull QueueSettings settings) { 27 | this.location = Objects.requireNonNull(location, "location must not be null"); 28 | this.settings = Objects.requireNonNull(settings, "settings must not be null"); 29 | } 30 | 31 | /** 32 | * Get queue location. 33 | * 34 | * @return Queue location. 35 | */ 36 | @Nonnull 37 | public QueueLocation getLocation() { 38 | return location; 39 | } 40 | 41 | /** 42 | * Get queue settings. 43 | * 44 | * @return Queue settings. 45 | */ 46 | @Nonnull 47 | public QueueSettings getSettings() { 48 | return settings; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return '{' + 54 | "location=" + location + 55 | ", settings=" + settings + 56 | '}'; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object obj) { 61 | if (this == obj) { 62 | return true; 63 | } 64 | if (obj == null || getClass() != obj.getClass()) { 65 | return false; 66 | } 67 | QueueConfig that = (QueueConfig) obj; 68 | return Objects.equals(location, that.location) && 69 | Objects.equals(settings, that.settings); 70 | } 71 | 72 | @Override 73 | public int hashCode() { 74 | return Objects.hash(location, settings); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/QueueConfigsReloader.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import ru.yoomoney.tech.dbqueue.config.QueueService; 6 | 7 | import javax.annotation.Nonnull; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * Dynamic reload of queue configuration. 15 | *

16 | * Reloads queue configuration if source files has been changed. 17 | * 18 | * @author Oleg Kandaurov 19 | * @since 12.10.2021 20 | */ 21 | public class QueueConfigsReloader { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(QueueConfigsReloader.class); 24 | 25 | @Nonnull 26 | private final QueueConfigsReader queueConfigsReader; 27 | @Nonnull 28 | private final QueueService queueService; 29 | @Nonnull 30 | private final List fileWatchers; 31 | 32 | /** 33 | * Constructor 34 | * 35 | * @param queueConfigsReader queue configuration parser 36 | * @param queueService queue service 37 | */ 38 | public QueueConfigsReloader(@Nonnull QueueConfigsReader queueConfigsReader, 39 | @Nonnull QueueService queueService) { 40 | this.queueConfigsReader = Objects.requireNonNull(queueConfigsReader, "queueConfigsReader"); 41 | this.queueService = Objects.requireNonNull(queueService, "queueService"); 42 | this.fileWatchers = queueConfigsReader.getConfigPaths().stream() 43 | .map(path -> new FileWatcher(path, this::reload)) 44 | .collect(Collectors.toList()); 45 | } 46 | 47 | private synchronized void reload() { 48 | try { 49 | List queueConfigs = queueConfigsReader.parse(); 50 | Map diff = queueService.updateQueueConfigs(queueConfigs); 51 | log.info("queue configuration updated: diff={}", diff); 52 | } catch (RuntimeException exc) { 53 | log.error("cannot reload queue configs", exc); 54 | } 55 | } 56 | 57 | /** 58 | * Starts automatic reload of queue configuration 59 | */ 60 | public synchronized void start() { 61 | fileWatchers.forEach(FileWatcher::startWatch); 62 | } 63 | 64 | /** 65 | * Stops automatic reload of queue configuration 66 | */ 67 | public synchronized void stop() { 68 | fileWatchers.forEach(FileWatcher::stopWatch); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/QueueId.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.util.Objects; 5 | 6 | /** 7 | * Queue identifier. 8 | * 9 | * @author Oleg Kandaurov 10 | * @since 27.09.2017 11 | */ 12 | public final class QueueId { 13 | 14 | @Nonnull 15 | private final String id; 16 | 17 | /** 18 | * Constructor 19 | * 20 | * @param id String representation of queue identifier. 21 | */ 22 | public QueueId(@Nonnull String id) { 23 | this.id = Objects.requireNonNull(id, "queue id must not be null"); 24 | } 25 | 26 | /** 27 | * Get string representation of queue identifier. 28 | * 29 | * @return Queue identifier. 30 | */ 31 | @Nonnull 32 | public String asString() { 33 | return id; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return id; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object obj) { 43 | if (this == obj) { 44 | return true; 45 | } 46 | if (obj == null || getClass() != obj.getClass()) { 47 | return false; 48 | } 49 | QueueId queueId = (QueueId) obj; 50 | return Objects.equals(id, queueId.id); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(id); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/QueueLocationParser.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | 9 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_ID_SEQUENCE; 10 | import static ru.yoomoney.tech.dbqueue.settings.QueueConfigsReader.SETTING_TABLE; 11 | 12 | /** 13 | * Parser for {@link QueueLocation} 14 | * 15 | * @author Oleg Kandaurov 16 | * @since 01.10.2021 17 | */ 18 | class QueueLocationParser { 19 | 20 | private final List errorMessages; 21 | 22 | /** 23 | * Constructor 24 | * 25 | * @param errorMessages list of error messages 26 | */ 27 | QueueLocationParser(@Nonnull List errorMessages) { 28 | this.errorMessages = Objects.requireNonNull(errorMessages, "errorMessages"); 29 | } 30 | 31 | /** 32 | * Parse settings 33 | * 34 | * @param queueId raw queue identifier 35 | * @param settings raw settings 36 | * @return settings or empty object in case of failure 37 | */ 38 | Optional parseQueueLocation(@Nonnull String queueId, @Nonnull Map settings) { 39 | Objects.requireNonNull(queueId, "queueId"); 40 | Objects.requireNonNull(settings, "settings"); 41 | try { 42 | QueueLocation.Builder queueLocation = QueueLocation.builder(); 43 | queueLocation.withQueueId(new QueueId(queueId)); 44 | settings.forEach((key, value) -> fillSettings(queueLocation, key, value)); 45 | return Optional.of(queueLocation.build()); 46 | } catch (RuntimeException exc) { 47 | errorMessages.add(String.format("cannot build queue location: queueId=%s, msg=%s", queueId, exc.getMessage())); 48 | return Optional.empty(); 49 | } 50 | } 51 | 52 | private void fillSettings(QueueLocation.Builder queueLocation, String name, String value) { 53 | try { 54 | switch (name) { 55 | case SETTING_TABLE: 56 | queueLocation.withTableName(value); 57 | return; 58 | case SETTING_ID_SEQUENCE: 59 | queueLocation.withIdSequence(value); 60 | return; 61 | default: 62 | return; 63 | 64 | } 65 | } catch (RuntimeException exc) { 66 | errorMessages.add(String.format("cannot parse setting: name=%s, value=%s, exception=%s", name, value, 67 | exc.getClass().getSimpleName() + '(' + exc.getMessage() + ')')); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /db-queue-core/src/main/java/ru/yoomoney/tech/dbqueue/settings/ReenqueueRetryType.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 4 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 5 | 6 | import java.time.Duration; 7 | 8 | /** 9 | * Type of the strategy, which computes the delay before 10 | * the next task execution if the task has to be brought back 11 | * {@link TaskExecutionResult.Type#REENQUEUE to the queue}. 12 | * 13 | * @author Dmitry Komarov 14 | * @since 21.05.2019 15 | */ 16 | public enum ReenqueueRetryType { 17 | 18 | /** 19 | * The task is deferred by the delay set manually with method 20 | * {@link TaskExecutionResult#reenqueue(Duration)} call. 21 | *

22 | * Default value for the task postponing strategy. 23 | *

24 | * Settings example: 25 | *

 26 |      * {@code db-queue.queueName.reenqueue-retry-type=manual}
 27 |      * 
28 | */ 29 | MANUAL, 30 | 31 | /** 32 | * The task is deferred by the delay set with the sequence of delays. 33 | * Delay is selected from the sequence according 34 | * to {@link TaskRecord#getReenqueueAttemptsCount() the number of task processing attempt}. 35 | * If the attempt number is bigger than the index of the last item in the sequence, 36 | * then the last item will be used. 37 | *

38 | * For example, let the following sequence is set out in the settings: 39 | *

 40 |      * {@code
 41 |      * db-queue.queueName.reenqueue-retry-type=sequential
 42 |      * db-queue.queueName.reenqueue-retry-plan=PT1S,PT10S,PT1M,P7D}
 43 |      * 
44 | * For the first attempt to defer the task a delay of 1 second will be chosen ({@code PT1S}), 45 | * for the second one it will be 10 seconds and so forth. 46 | * For the fifth attempt and all the next after the delay will be 7 days. 47 | */ 48 | SEQUENTIAL, 49 | 50 | /** 51 | * The task is deferred by the fixed delay, which is set in configuration. 52 | *

53 | * Settings example: 54 | *

 55 |      * {@code
 56 |      * db-queue.queueName.reenqueue-retry-type=fixed
 57 |      * db-queue.queueName.reenqueue-retry-delay=PT10S}
 58 |      * 
59 | * Means that for each attempt the task will be deferred for 10 seconds. 60 | */ 61 | FIXED, 62 | 63 | /** 64 | * The task is deferred by the delay set using an arithmetic progression. 65 | * The term of progression selected according 66 | * to {@link TaskRecord#getReenqueueAttemptsCount() the number of attempt to postpone the task processing}. 67 | *

68 | * The progression is set by a pair of values: the initial term ({@code reenqueue-retry-initial-delay}) 69 | * and the difference ({@code reenqueue-retry-step}). 70 | *

71 | * Settings example: 72 | *

 73 |      * {@code
 74 |      * db-queue.queueName.reenqueue-retry-type=arithmetic
 75 |      * db-queue.queueName.reenqueue-retry-initial-delay=PT1S
 76 |      * db-queue.queueName.reenqueue-retry-step=PT2S}
 77 |      * 
78 | * Means that the task will be deferred with following delays: {@code 1 second, 3 seconds, 5 seconds, 7 seconds, ...} 79 | */ 80 | ARITHMETIC, 81 | 82 | /** 83 | * The task is deferred by the delay set using a geometric progression 84 | * The term of progression selected according to 85 | * {@link TaskRecord#getReenqueueAttemptsCount() the number of attempt to postpone the task processing}. 86 | *

87 | * The progression is set by a pair of values: the initial term and the integer denominator. 88 | *

89 | * Settings example: 90 | *

 91 |      * {@code
 92 |      * db-queue.queueName.reenqueue-retry-type=geometric
 93 |      * db-queue.queueName.reenqueue-retry-initial-delay=PT1S
 94 |      * db-queue.queueName.reenqueue-retry-ratio=2}
 95 |      * 
96 | * Means that the task will be deferred with following delays: {@code 1 second, 2 seconds, 4 seconds, 8 seconds, ...} 97 | */ 98 | GEOMETRIC 99 | } 100 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/ArchitectureTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue; 2 | 3 | import com.tngtech.archunit.core.domain.JavaClasses; 4 | import com.tngtech.archunit.core.importer.ClassFileImporter; 5 | import com.tngtech.archunit.core.importer.ImportOptions; 6 | import com.tngtech.archunit.lang.ArchRule; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.util.Arrays; 11 | import java.util.stream.Collectors; 12 | 13 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; 14 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; 15 | 16 | /** 17 | * @author Oleg Kandaurov 18 | * @since 03.08.2017 19 | */ 20 | public class ArchitectureTest { 21 | 22 | private static final String BASE_PACKAGE = "ru.yoomoney.tech.dbqueue"; 23 | 24 | private JavaClasses classes; 25 | 26 | @Before 27 | public void importClasses() { 28 | classes = new ClassFileImporter(new ImportOptions()) 29 | .importPackages(BASE_PACKAGE); 30 | } 31 | 32 | @Test 33 | public void test2() { 34 | ArchRule rule = classes().that().resideInAnyPackage( 35 | fullNames("api")) 36 | .should().accessClassesThat().resideInAnyPackage(fullNames("api..", "settings..")) 37 | .orShould().accessClassesThat().resideInAnyPackage("java..") 38 | .because("api must not depend on implementation details"); 39 | rule.check(classes); 40 | } 41 | 42 | @Test 43 | public void test3() { 44 | ArchRule rule = classes().that().resideInAnyPackage( 45 | fullNames("settings")) 46 | .should().accessClassesThat().resideInAnyPackage(fullNames("settings")) 47 | .orShould().accessClassesThat().resideInAnyPackage("java..") 48 | .because("settings must not depend on implementation details"); 49 | rule.check(classes); 50 | } 51 | 52 | @Test 53 | public void test4() { 54 | ArchRule rule = noClasses().that().resideInAnyPackage( 55 | fullNames("settings..", "api..", "dao..", "spring..")) 56 | .should().accessClassesThat().resideInAnyPackage(fullNames("internal..")) 57 | .because("public classes must not depend on internal details"); 58 | rule.check(classes); 59 | } 60 | 61 | 62 | private static String[] fullNames(String... relativeName) { 63 | return Arrays.stream(relativeName).map(name -> BASE_PACKAGE + "." + name) 64 | .collect(Collectors.toList()).toArray(new String[relativeName.length]); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/api/EnqueueParamsTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import nl.jqno.equalsverifier.Warning; 5 | import org.junit.Test; 6 | 7 | /** 8 | * @author Oleg Kandaurov 9 | * @since 10.08.2017 10 | */ 11 | public class EnqueueParamsTest { 12 | 13 | @Test 14 | public void should_define_correct_equals_hashcode() throws Exception { 15 | EqualsVerifier.forClass(EnqueueParams.class).suppress(Warning.NONFINAL_FIELDS).verify(); 16 | } 17 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/api/QueueShardIdTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import org.junit.Test; 5 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 6 | 7 | /** 8 | * @author Oleg Kandaurov 9 | * @since 10.08.2017 10 | */ 11 | public class QueueShardIdTest { 12 | 13 | @Test 14 | public void should_define_correct_equals_hashcode() throws Exception { 15 | EqualsVerifier.forClass(QueueShardId.class).verify(); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/api/TaskExecutionResultTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import org.junit.Test; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 10.08.2017 9 | */ 10 | public class TaskExecutionResultTest { 11 | 12 | @Test 13 | public void should_define_correct_equals_hashcode() throws Exception { 14 | EqualsVerifier.forClass(TaskExecutionResult.class).verify(); 15 | } 16 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/api/TaskRecordTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import org.junit.Test; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 10.08.2017 9 | */ 10 | public class TaskRecordTest { 11 | 12 | @Test 13 | public void should_define_correct_equals_hashcode() throws Exception { 14 | EqualsVerifier.forClass(TaskRecord.class).verify(); 15 | } 16 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/api/TaskTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import org.junit.Test; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 10.08.2017 9 | */ 10 | public class TaskTest { 11 | 12 | @Test 13 | public void should_define_correct_equals_hashcode() throws Exception { 14 | EqualsVerifier.forClass(Task.class).verify(); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/api/impl/MonitoringQueueProducerTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api.impl; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.mockito.invocation.InvocationOnMock; 6 | import org.mockito.stubbing.Answer; 7 | import ru.yoomoney.tech.dbqueue.api.EnqueueParams; 8 | import ru.yoomoney.tech.dbqueue.api.EnqueueResult; 9 | import ru.yoomoney.tech.dbqueue.api.QueueProducer; 10 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 11 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.nio.file.StandardOpenOption; 18 | import java.time.Clock; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | import static org.hamcrest.CoreMatchers.equalTo; 23 | import static org.junit.Assert.assertThat; 24 | import static org.mockito.Mockito.mock; 25 | import static org.mockito.Mockito.when; 26 | 27 | public class MonitoringQueueProducerTest { 28 | 29 | public static final Path LOG_PATH = Paths.get("target/monitoring-queue-producer.log"); 30 | 31 | @Before 32 | public void setUp() throws Exception { 33 | if (Files.exists(LOG_PATH)) { 34 | Files.write(LOG_PATH, new byte[0], StandardOpenOption.TRUNCATE_EXISTING); 35 | } 36 | } 37 | 38 | @Test 39 | public void should_invoke_monitoring_callback_and_print_logs() throws IOException { 40 | Clock clock = mock(Clock.class); 41 | 42 | EnqueueResult expectedResult = EnqueueResult.builder().withShardId(new QueueShardId("main")).withEnqueueId(1L).build(); 43 | QueueProducer producer = mock(QueueProducer.class); 44 | 45 | MonitoringQueueProducer monitoringProducer = new MonitoringQueueProducer<>(producer, new QueueId("test"), 46 | (enqueueResult, time) -> { 47 | assertThat(enqueueResult, equalTo(expectedResult)); 48 | assertThat(time, equalTo(4L)); 49 | }, clock); 50 | 51 | when(producer.enqueue(EnqueueParams.create("1"))).thenReturn(expectedResult); 52 | when(clock.millis()).thenAnswer(new Answer() { 53 | private int count = 0; 54 | 55 | public Object answer(InvocationOnMock invocation) { 56 | count++; 57 | if (count == 1) { 58 | return 1L; 59 | } else if (count == 2) { 60 | return 5L; 61 | } else { 62 | throw new IllegalStateException(); 63 | } 64 | } 65 | }); 66 | 67 | EnqueueResult actualResult = monitoringProducer.enqueue(EnqueueParams.create("1")); 68 | assertThat(actualResult, equalTo(expectedResult)); 69 | 70 | List logFile = Files.readAllLines(LOG_PATH); 71 | assertThat(logFile, equalTo(Arrays.asList( 72 | "INFO [MonitoringQueueProducer] enqueuing task: queue=test, delay=PT0S", 73 | "INFO [MonitoringQueueProducer] task enqueued: id=1, queueShardId=main"))); 74 | } 75 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/api/impl/ShardingQueueProducerTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api.impl; 2 | 3 | import org.hamcrest.CoreMatchers; 4 | import org.junit.Test; 5 | import ru.yoomoney.tech.dbqueue.api.EnqueueParams; 6 | import ru.yoomoney.tech.dbqueue.api.EnqueueResult; 7 | import ru.yoomoney.tech.dbqueue.api.QueueShardRouter; 8 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 9 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 10 | import ru.yoomoney.tech.dbqueue.dao.QueueDao; 11 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 12 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 13 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 14 | import ru.yoomoney.tech.dbqueue.stub.StubDatabaseAccessLayer; 15 | import ru.yoomoney.tech.dbqueue.stub.TestFixtures; 16 | 17 | import java.util.Objects; 18 | 19 | import static org.junit.Assert.assertThat; 20 | import static org.mockito.ArgumentMatchers.eq; 21 | import static org.mockito.Mockito.when; 22 | 23 | public class ShardingQueueProducerTest { 24 | 25 | @Test 26 | public void should_insert_task_on_designated_shard() { 27 | 28 | StubDatabaseAccessLayer stubDatabaseAccessLayer = new StubDatabaseAccessLayer(); 29 | QueueShard firstShard = new QueueShard<>(new QueueShardId("first"), 30 | stubDatabaseAccessLayer); 31 | QueueShard secondShard = new QueueShard<>(new QueueShardId("second"), 32 | stubDatabaseAccessLayer); 33 | 34 | QueueConfig queueConfig = new QueueConfig( 35 | QueueLocation.builder().withTableName("testTable") 36 | .withQueueId(new QueueId("main")).build(), 37 | TestFixtures.createQueueSettings().build()); 38 | 39 | QueueDao queueDao = stubDatabaseAccessLayer.getQueueDao(); 40 | when(queueDao.enqueue(eq(queueConfig.getLocation()), eq(EnqueueParams.create("1")))).thenReturn(11L); 41 | when(queueDao.enqueue(eq(queueConfig.getLocation()), eq(EnqueueParams.create("2")))).thenReturn(22L); 42 | 43 | ShardingQueueProducer queueProducer = new ShardingQueueProducer<>( 44 | queueConfig, NoopPayloadTransformer.getInstance(), new StubQueueShardRouter(firstShard, secondShard)); 45 | 46 | EnqueueResult enqueueResult1 = queueProducer.enqueue(EnqueueParams.create("1")); 47 | assertThat(enqueueResult1, CoreMatchers.equalTo(EnqueueResult.builder().withEnqueueId(11L) 48 | .withShardId(firstShard.getShardId()).build())); 49 | 50 | EnqueueResult enqueueResult2 = queueProducer.enqueue(EnqueueParams.create("2")); 51 | assertThat(enqueueResult2, CoreMatchers.equalTo(EnqueueResult.builder().withEnqueueId(22L) 52 | .withShardId(secondShard.getShardId()).build())); 53 | 54 | } 55 | 56 | private static class StubQueueShardRouter implements QueueShardRouter { 57 | 58 | private final QueueShard firstShard; 59 | private final QueueShard secondShard; 60 | 61 | public StubQueueShardRouter(QueueShard firstShard, 62 | QueueShard secondShard) { 63 | this.firstShard = firstShard; 64 | this.secondShard = secondShard; 65 | } 66 | 67 | @Override 68 | public QueueShard resolveShard(EnqueueParams enqueueParams) { 69 | Objects.requireNonNull(enqueueParams.getPayload()); 70 | if (enqueueParams.getPayload().equals("1")) { 71 | return firstShard; 72 | } else if (enqueueParams.getPayload().equals("2")) { 73 | return secondShard; 74 | } 75 | throw new IllegalStateException(); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/api/impl/SingleQueueShardRouterTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.api.impl; 2 | 3 | import org.junit.Test; 4 | import ru.yoomoney.tech.dbqueue.api.EnqueueParams; 5 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 6 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 7 | import ru.yoomoney.tech.dbqueue.stub.StubDatabaseAccessLayer; 8 | 9 | import static org.hamcrest.CoreMatchers.equalTo; 10 | import static org.junit.Assert.assertThat; 11 | 12 | public class SingleQueueShardRouterTest { 13 | 14 | @Test 15 | public void should_return_single_shard() { 16 | QueueShard main = new QueueShard<>(new QueueShardId("main"), 17 | new StubDatabaseAccessLayer()); 18 | SingleQueueShardRouter router = new SingleQueueShardRouter<>(main); 19 | assertThat(router.resolveShard(EnqueueParams.create("1")), equalTo(main)); 20 | } 21 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/config/DirectExecutor.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.concurrent.AbstractExecutorService; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * @author Oleg Kandaurov 10 | * @since 12.10.2019 11 | */ 12 | class DirectExecutor extends AbstractExecutorService { 13 | 14 | @Override 15 | public void execute(Runnable command) { 16 | command.run(); 17 | } 18 | 19 | @Override 20 | public void shutdown() { 21 | 22 | } 23 | 24 | @Override 25 | public List shutdownNow() { 26 | return Collections.emptyList(); 27 | } 28 | 29 | @Override 30 | public boolean isShutdown() { 31 | return false; 32 | } 33 | 34 | @Override 35 | public boolean isTerminated() { 36 | return false; 37 | } 38 | 39 | @Override 40 | public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { 41 | return false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/config/QueueTableSchemaTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Collections; 6 | 7 | import static org.hamcrest.CoreMatchers.equalTo; 8 | import static org.junit.Assert.assertThat; 9 | 10 | /** 11 | * @author Oleg Kandaurov 12 | * @since 16.10.2019 13 | */ 14 | 15 | public class QueueTableSchemaTest { 16 | 17 | @Test 18 | public void should_filter_special_chars() { 19 | QueueTableSchema schema = QueueTableSchema.builder() 20 | .withIdField("qid !@#$%^&*()_+-=1\n;'][{}") 21 | .withQueueNameField("qn !@#$%^&*()_+-=1\n;'][{}") 22 | .withPayloadField("pl !@#$%^&*()_+-=1\n;'][{}") 23 | .withCreatedAtField("ct !@#$%^&*()_+-=1\n;'][{}") 24 | .withNextProcessAtField("pt !@#$%^&*()_+-=1\n;'][{}") 25 | .withAttemptField("at !@#$%^&*()_+-=1\n;'][{}") 26 | .withReenqueueAttemptField("rat !@#$%^&*()_+-=1\n;'][{}") 27 | .withTotalAttemptField("tat !@#$%^&*()_+-=1\n;'][{}") 28 | .withExtFields(Collections.singletonList("tr !@#$%^&*()_+-=1\n;'][{}")) 29 | .build(); 30 | assertThat(schema.getIdField(), equalTo("qid_1")); 31 | assertThat(schema.getQueueNameField(), equalTo("qn_1")); 32 | assertThat(schema.getPayloadField(), equalTo("pl_1")); 33 | assertThat(schema.getCreatedAtField(), equalTo("ct_1")); 34 | assertThat(schema.getNextProcessAtField(), equalTo("pt_1")); 35 | assertThat(schema.getAttemptField(), equalTo("at_1")); 36 | assertThat(schema.getReenqueueAttemptField(), equalTo("rat_1")); 37 | assertThat(schema.getTotalAttemptField(), equalTo("tat_1")); 38 | assertThat(schema.getExtFields().get(0), equalTo("tr_1")); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/config/impl/LoggingThreadLifecycleListenerTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.config.impl; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import ru.yoomoney.tech.dbqueue.config.QueueShardId; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 7 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.nio.file.StandardOpenOption; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | import static org.hamcrest.CoreMatchers.equalTo; 18 | import static org.junit.Assert.assertThat; 19 | 20 | public class LoggingThreadLifecycleListenerTest { 21 | 22 | public static final Path LOG_PATH = Paths.get("target/thread-listener.log"); 23 | 24 | @Before 25 | public void setUp() throws Exception { 26 | if (Files.exists(LOG_PATH)) { 27 | Files.write(LOG_PATH, new byte[0], StandardOpenOption.TRUNCATE_EXISTING); 28 | } 29 | } 30 | 31 | @Test 32 | public void should_log_thread_lifecycle() throws IOException { 33 | LoggingThreadLifecycleListener listener = new LoggingThreadLifecycleListener(); 34 | QueueShardId shardId = new QueueShardId("shardId1"); 35 | QueueLocation location = QueueLocation.builder() 36 | .withTableName("table1").withQueueId(new QueueId("queueId1")).build(); 37 | listener.started(shardId, location); 38 | listener.executed(shardId, location, true, 42L); 39 | listener.finished(shardId, location); 40 | listener.crashed(shardId, location, null); 41 | 42 | List logFile = Files.readAllLines(LOG_PATH); 43 | assertThat(logFile, equalTo(Collections.singletonList("ERROR [LoggingThreadLifecycleListener] fatal error in queue thread: shardId=shardId1, location={id=queueId1,table=table1}"))); 44 | } 45 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/internal/processing/DelegatedSingleQueueLoopExecution.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | import java.time.Duration; 4 | 5 | public class DelegatedSingleQueueLoopExecution implements QueueLoop { 6 | 7 | private final QueueLoop delegate; 8 | private int attemptCount = 0; 9 | 10 | DelegatedSingleQueueLoopExecution(QueueLoop delegate) { 11 | this.delegate = delegate; 12 | } 13 | 14 | @Override 15 | public void doRun(Runnable runnable) { 16 | delegate.doRun(() -> { 17 | if (attemptCount > 0) { 18 | Thread.currentThread().interrupt(); 19 | return; 20 | } 21 | attemptCount++; 22 | runnable.run(); 23 | }); 24 | } 25 | 26 | @Override 27 | public void doContinue() { 28 | delegate.doContinue(); 29 | } 30 | 31 | @Override 32 | public void doWait(Duration timeout, WaitInterrupt waitInterrupt) { 33 | delegate.doWait(timeout, waitInterrupt); 34 | } 35 | 36 | @Override 37 | public void pause() { 38 | delegate.pause(); 39 | } 40 | 41 | @Override 42 | public void unpause() { 43 | delegate.unpause(); 44 | } 45 | 46 | @Override 47 | public boolean isPaused() { 48 | return delegate.isPaused(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/internal/processing/SyncQueueLoop.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | import java.time.Duration; 4 | 5 | public class SyncQueueLoop implements QueueLoop { 6 | @Override 7 | public void doRun(Runnable runnable) { 8 | runnable.run(); 9 | } 10 | 11 | @Override 12 | public void doContinue() { 13 | 14 | } 15 | 16 | @Override 17 | public void doWait(Duration timeout, WaitInterrupt waitInterrupt) { 18 | 19 | } 20 | 21 | @Override 22 | public boolean isPaused() { 23 | return false; 24 | } 25 | 26 | @Override 27 | public void pause() { 28 | 29 | } 30 | 31 | @Override 32 | public void unpause() { 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/internal/processing/TimeLimiterTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.processing; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import ru.yoomoney.tech.dbqueue.stub.FakeMillisTimeProvider; 6 | 7 | import java.time.Duration; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | /** 13 | * @author Oleg Kandaurov 14 | * @since 18.10.2019 15 | */ 16 | public class TimeLimiterTest { 17 | 18 | @Test 19 | public void should_not_execute_when_timeout_is_zero() { 20 | TimeLimiter timeLimiter = new TimeLimiter(new FakeMillisTimeProvider(Collections.emptyList()), Duration.ZERO); 21 | timeLimiter.execute(ignored -> Assert.fail("should not invoke when duration is zero")); 22 | } 23 | 24 | @Test 25 | public void should_drain_timeout_to_zero() { 26 | Duration timeout = Duration.ofMillis(10); 27 | AtomicInteger executionCount = new AtomicInteger(0); 28 | TimeLimiter timeLimiter = new TimeLimiter(new FakeMillisTimeProvider(Arrays.asList(0L, 3L, 3L, 11L)), timeout); 29 | timeLimiter.execute(remainingTimeout -> { 30 | executionCount.incrementAndGet(); 31 | Assert.assertEquals(timeout, remainingTimeout); 32 | }); 33 | timeLimiter.execute(remainingTimeout -> { 34 | executionCount.incrementAndGet(); 35 | Assert.assertEquals(Duration.ofMillis(7), remainingTimeout); 36 | }); 37 | timeLimiter.execute(ignored -> Assert.fail("should not invoke when duration is zero")); 38 | Assert.assertEquals(2, executionCount.get()); 39 | } 40 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/internal/runner/QueueRunnerInSeparateTransactionsTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.runner; 2 | 3 | import org.junit.Test; 4 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 5 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 6 | import ru.yoomoney.tech.dbqueue.internal.processing.QueueProcessingStatus; 7 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskPicker; 8 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskProcessor; 9 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 10 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 11 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 12 | import ru.yoomoney.tech.dbqueue.stub.TestFixtures; 13 | 14 | import java.time.Duration; 15 | 16 | import static org.hamcrest.CoreMatchers.equalTo; 17 | import static org.junit.Assert.assertThat; 18 | import static org.mockito.Mockito.mock; 19 | import static org.mockito.Mockito.verify; 20 | import static org.mockito.Mockito.verifyNoInteractions; 21 | import static org.mockito.Mockito.when; 22 | 23 | /** 24 | * @author Oleg Kandaurov 25 | * @since 04.08.2017 26 | */ 27 | public class QueueRunnerInSeparateTransactionsTest { 28 | 29 | private static final QueueLocation testLocation1 = 30 | QueueLocation.builder().withTableName("queue_test") 31 | .withQueueId(new QueueId("test_queue1")).build(); 32 | 33 | @Test 34 | public void should_wait_notasktimeout_when_no_task_found() { 35 | Duration betweenTaskTimeout = Duration.ofHours(1L); 36 | Duration noTaskTimeout = Duration.ofMillis(5L); 37 | 38 | QueueConsumer queueConsumer = mock(QueueConsumer.class); 39 | TaskPicker taskPicker = mock(TaskPicker.class); 40 | when(taskPicker.pickTask()).thenReturn(null); 41 | TaskProcessor taskProcessor = mock(TaskProcessor.class); 42 | 43 | when(queueConsumer.getQueueConfig()).thenReturn(new QueueConfig(testLocation1, 44 | TestFixtures.createQueueSettings().withPollSettings(TestFixtures.createPollSettings() 45 | .withBetweenTaskTimeout(betweenTaskTimeout).withNoTaskTimeout(noTaskTimeout).build()).build())); 46 | QueueProcessingStatus status = new QueueRunnerInSeparateTransactions(taskPicker, taskProcessor).runQueue(queueConsumer); 47 | 48 | assertThat(status, equalTo(QueueProcessingStatus.SKIPPED)); 49 | 50 | verify(taskPicker).pickTask(); 51 | verifyNoInteractions(taskProcessor); 52 | } 53 | 54 | @Test 55 | public void should_wait_betweentasktimeout_when_task_found() { 56 | Duration betweenTaskTimeout = Duration.ofHours(1L); 57 | Duration noTaskTimeout = Duration.ofMillis(5L); 58 | 59 | QueueConsumer queueConsumer = mock(QueueConsumer.class); 60 | TaskPicker taskPicker = mock(TaskPicker.class); 61 | TaskRecord taskRecord = TaskRecord.builder().build(); 62 | when(taskPicker.pickTask()).thenReturn(taskRecord); 63 | TaskProcessor taskProcessor = mock(TaskProcessor.class); 64 | 65 | 66 | when(queueConsumer.getQueueConfig()).thenReturn(new QueueConfig(testLocation1, 67 | TestFixtures.createQueueSettings().withPollSettings(TestFixtures.createPollSettings() 68 | .withBetweenTaskTimeout(betweenTaskTimeout).withNoTaskTimeout(noTaskTimeout).build()).build())); 69 | QueueProcessingStatus status = new QueueRunnerInSeparateTransactions(taskPicker, taskProcessor).runQueue(queueConsumer); 70 | 71 | assertThat(status, equalTo(QueueProcessingStatus.PROCESSED)); 72 | 73 | verify(taskPicker).pickTask(); 74 | verify(taskProcessor).processTask(queueConsumer, taskRecord); 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/internal/runner/QueueRunnerInTransactionTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.internal.runner; 2 | 3 | import org.junit.Test; 4 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 5 | import ru.yoomoney.tech.dbqueue.api.TaskRecord; 6 | import ru.yoomoney.tech.dbqueue.config.QueueShard; 7 | import ru.yoomoney.tech.dbqueue.internal.processing.QueueProcessingStatus; 8 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskPicker; 9 | import ru.yoomoney.tech.dbqueue.internal.processing.TaskProcessor; 10 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 11 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 12 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 13 | import ru.yoomoney.tech.dbqueue.stub.StubDatabaseAccessLayer; 14 | import ru.yoomoney.tech.dbqueue.stub.TestFixtures; 15 | 16 | import java.time.Duration; 17 | 18 | import static org.hamcrest.CoreMatchers.equalTo; 19 | import static org.junit.Assert.assertThat; 20 | import static org.mockito.Mockito.mock; 21 | import static org.mockito.Mockito.verify; 22 | import static org.mockito.Mockito.verifyNoInteractions; 23 | import static org.mockito.Mockito.when; 24 | 25 | /** 26 | * @author Oleg Kandaurov 27 | * @since 04.08.2017 28 | */ 29 | public class QueueRunnerInTransactionTest { 30 | 31 | private static final QueueLocation testLocation1 = 32 | QueueLocation.builder().withTableName("queue_test") 33 | .withQueueId(new QueueId("test_queue1")).build(); 34 | 35 | @Test 36 | public void should_wait_notasktimeout_when_no_task_found() throws Exception { 37 | Duration betweenTaskTimeout = Duration.ofHours(1L); 38 | Duration noTaskTimeout = Duration.ofMillis(5L); 39 | 40 | QueueConsumer queueConsumer = mock(QueueConsumer.class); 41 | TaskPicker taskPicker = mock(TaskPicker.class); 42 | when(taskPicker.pickTask()).thenReturn(null); 43 | TaskProcessor taskProcessor = mock(TaskProcessor.class); 44 | QueueShard queueShard = mock(QueueShard.class); 45 | when(queueShard.getDatabaseAccessLayer()).thenReturn(new StubDatabaseAccessLayer()); 46 | 47 | when(queueConsumer.getQueueConfig()).thenReturn(new QueueConfig(testLocation1, 48 | TestFixtures.createQueueSettings().withPollSettings(TestFixtures.createPollSettings() 49 | .withBetweenTaskTimeout(betweenTaskTimeout).withNoTaskTimeout(noTaskTimeout).build()).build())); 50 | QueueProcessingStatus status = new QueueRunnerInTransaction(taskPicker, taskProcessor, queueShard).runQueue(queueConsumer); 51 | 52 | assertThat(status, equalTo(QueueProcessingStatus.SKIPPED)); 53 | 54 | verify(queueShard).getDatabaseAccessLayer(); 55 | verify(taskPicker).pickTask(); 56 | verifyNoInteractions(taskProcessor); 57 | } 58 | 59 | @Test 60 | public void should_wait_betweentasktimeout_when_task_found() throws Exception { 61 | Duration betweenTaskTimeout = Duration.ofHours(1L); 62 | Duration noTaskTimeout = Duration.ofMillis(5L); 63 | 64 | QueueConsumer queueConsumer = mock(QueueConsumer.class); 65 | TaskPicker taskPicker = mock(TaskPicker.class); 66 | TaskRecord taskRecord = TaskRecord.builder().build(); 67 | when(taskPicker.pickTask()).thenReturn(taskRecord); 68 | TaskProcessor taskProcessor = mock(TaskProcessor.class); 69 | QueueShard queueShard = mock(QueueShard.class); 70 | when(queueShard.getDatabaseAccessLayer()).thenReturn(new StubDatabaseAccessLayer()); 71 | 72 | 73 | when(queueConsumer.getQueueConfig()).thenReturn(new QueueConfig(testLocation1, 74 | TestFixtures.createQueueSettings().withPollSettings(TestFixtures.createPollSettings() 75 | .withBetweenTaskTimeout(betweenTaskTimeout).withNoTaskTimeout(noTaskTimeout).build()).build())); 76 | QueueProcessingStatus queueProcessingStatus = new QueueRunnerInTransaction(taskPicker, taskProcessor, queueShard).runQueue(queueConsumer); 77 | 78 | assertThat(queueProcessingStatus, equalTo(QueueProcessingStatus.PROCESSED)); 79 | 80 | verify(queueShard).getDatabaseAccessLayer(); 81 | verify(taskPicker).pickTask(); 82 | verify(taskProcessor).processTask(queueConsumer, taskRecord); 83 | } 84 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/settings/DynamicSettingTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import org.junit.Test; 4 | 5 | import javax.annotation.Nonnull; 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | import java.util.function.BiFunction; 10 | 11 | import static org.hamcrest.CoreMatchers.equalTo; 12 | import static org.hamcrest.CoreMatchers.not; 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | 15 | public class DynamicSettingTest { 16 | 17 | @Test 18 | public void should_not_invoke_observer_when_no_changes() { 19 | AtomicBoolean observerInvoked = new AtomicBoolean(false); 20 | SimpleDynamicSetting oldSetting = new SimpleDynamicSetting("old"); 21 | oldSetting.registerObserver((oldVal, newVal) -> { 22 | observerInvoked.set(true); 23 | }); 24 | SimpleDynamicSetting newSetting = new SimpleDynamicSetting("old"); 25 | Optional diff = oldSetting.setValue(newSetting); 26 | assertThat(diff, equalTo(Optional.empty())); 27 | assertThat(observerInvoked.get(), equalTo(false)); 28 | assertThat(oldSetting, equalTo(newSetting)); 29 | } 30 | 31 | @Test 32 | public void should_invoke_observer_when_setting_changed() { 33 | AtomicBoolean observerInvoked = new AtomicBoolean(false); 34 | SimpleDynamicSetting oldSetting = new SimpleDynamicSetting("old"); 35 | oldSetting.registerObserver((oldVal, newVal) -> { 36 | observerInvoked.set(true); 37 | }); 38 | SimpleDynamicSetting newSetting = new SimpleDynamicSetting("new"); 39 | Optional diff = oldSetting.setValue(newSetting); 40 | assertThat(diff, equalTo(Optional.of("new { 49 | throw new RuntimeException("exc"); 50 | }); 51 | SimpleDynamicSetting newSetting = new SimpleDynamicSetting("new"); 52 | Optional diff = oldSetting.setValue(newSetting); 53 | assertThat(diff, equalTo(Optional.empty())); 54 | assertThat(oldSetting, not(equalTo(newSetting))); 55 | } 56 | 57 | private static class SimpleDynamicSetting extends DynamicSetting { 58 | 59 | private String text; 60 | 61 | private SimpleDynamicSetting(String text) { 62 | this.text = text; 63 | } 64 | 65 | @Nonnull 66 | @Override 67 | protected String getName() { 68 | return "simple"; 69 | } 70 | 71 | @Nonnull 72 | @Override 73 | protected BiFunction getDiffEvaluator() { 74 | return (oldVal, newVal) -> newVal.text + '<' + oldVal.text; 75 | } 76 | 77 | @Nonnull 78 | @Override 79 | protected SimpleDynamicSetting getThis() { 80 | return this; 81 | } 82 | 83 | @Override 84 | protected void copyFields(@Nonnull SimpleDynamicSetting newValue) { 85 | this.text = newValue.text; 86 | } 87 | 88 | @Override 89 | public boolean equals(Object o) { 90 | if (this == o) return true; 91 | if (o == null || getClass() != o.getClass()) return false; 92 | SimpleDynamicSetting that = (SimpleDynamicSetting) o; 93 | return Objects.equals(text, that.text); 94 | } 95 | 96 | @Override 97 | public int hashCode() { 98 | return Objects.hash(text); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/settings/ExtSettingsTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import nl.jqno.equalsverifier.Warning; 5 | import org.junit.Test; 6 | 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | import static org.hamcrest.CoreMatchers.equalTo; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class ExtSettingsTest { 15 | 16 | @Test 17 | public void should_define_correct_equals_hashcode() { 18 | EqualsVerifier.forClass(ExtSettings.class) 19 | .withIgnoredFields("observers") 20 | .suppress(Warning.NONFINAL_FIELDS) 21 | .usingGetClass().verify(); 22 | } 23 | 24 | @Test 25 | public void should_set_value() { 26 | Map oldMap = new LinkedHashMap<>(); 27 | oldMap.put("same", "1"); 28 | oldMap.put("old", "0"); 29 | Map newMap = new LinkedHashMap<>(); 30 | newMap.put("same", "2"); 31 | newMap.put("new", "3"); 32 | ExtSettings oldValue = ExtSettings.builder().withSettings(oldMap).build(); 33 | ExtSettings newValue = ExtSettings.builder().withSettings(newMap).build(); 34 | Optional diff = oldValue.setValue(newValue); 35 | assertThat(diff, equalTo(Optional.of("extSettings(same=2<1,new=3 diff = oldValue.setValue(newValue); 32 | assertThat(diff, equalTo(Optional.of("failureSettings(retryType=ARITHMETIC_BACKOFF { 30 | fileHasChanged.set(true); 31 | }); 32 | fileWatcher.startWatch(); 33 | // wait for executor start 34 | Thread.sleep(500); 35 | Files.write(tempFile, "2".getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.APPEND); 36 | long startTime = System.currentTimeMillis(); 37 | long elapsed = 0; 38 | while (Duration.ofMillis(elapsed).getSeconds() < 2 && !fileHasChanged.get()) { 39 | elapsed = System.currentTimeMillis() - startTime; 40 | Thread.sleep(100); 41 | } 42 | fileWatcher.stopWatch(); 43 | assertThat(fileHasChanged.get(), equalTo(true)); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/settings/PollSettingsTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import nl.jqno.equalsverifier.Warning; 5 | import org.junit.Test; 6 | 7 | import java.time.Duration; 8 | import java.util.Optional; 9 | 10 | import static org.hamcrest.CoreMatchers.equalTo; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class PollSettingsTest { 14 | 15 | @Test 16 | public void should_define_correct_equals_hashcode() { 17 | EqualsVerifier.forClass(PollSettings.class) 18 | .withIgnoredFields("observers") 19 | .suppress(Warning.NONFINAL_FIELDS) 20 | .usingGetClass().verify(); 21 | } 22 | 23 | @Test 24 | public void should_set_value() { 25 | PollSettings oldValue = PollSettings.builder().withBetweenTaskTimeout(Duration.ofSeconds(1)) 26 | .withNoTaskTimeout(Duration.ofSeconds(2)).withFatalCrashTimeout(Duration.ofSeconds(3)).build(); 27 | PollSettings newValue = PollSettings.builder().withBetweenTaskTimeout(Duration.ofSeconds(4)) 28 | .withNoTaskTimeout(Duration.ofSeconds(5)).withFatalCrashTimeout(Duration.ofSeconds(6)).build(); 29 | Optional diff = oldValue.setValue(newValue); 30 | assertThat(diff, equalTo(Optional.of("pollSettings(betweenTaskTimeout=PT4S diff = oldValue.setValue(newValue); 29 | assertThat(diff, equalTo(Optional.of("processingSettings(threadCount=0<1,processingMode=SEPARATE_TRANSACTIONS paths) { 45 | return new QueueConfigsReader(paths, "q", 46 | () -> ProcessingSettings.builder() 47 | .withProcessingMode(ProcessingMode.SEPARATE_TRANSACTIONS), 48 | () -> PollSettings.builder() 49 | .withBetweenTaskTimeout(Duration.ofSeconds(9)) 50 | .withNoTaskTimeout(Duration.ofSeconds(99)) 51 | .withFatalCrashTimeout(Duration.ofSeconds(999)), 52 | () -> FailureSettings.builder() 53 | .withRetryInterval(Duration.ofMinutes(9)).withRetryType(FailRetryType.GEOMETRIC_BACKOFF), 54 | () -> ReenqueueSettings.builder() 55 | .withRetryType(ReenqueueRetryType.MANUAL)); 56 | } 57 | 58 | @Test 59 | public void should_not_reload_bad_configs() throws Exception { 60 | Path configPath = write(""); 61 | 62 | QueueService queueService = mock(QueueService.class); 63 | QueueConfigsReader reader = spy(createReader(Collections.singletonList(configPath))); 64 | 65 | QueueConfigsReloader reloader = new QueueConfigsReloader(reader, queueService); 66 | reloader.start(); 67 | Files.write(configPath, "q.testname.table=foo".getBytes(StandardCharsets.UTF_8)); 68 | Thread.sleep(1000); 69 | reloader.stop(); 70 | verify(reader, atLeastOnce()).parse(); 71 | verifyNoInteractions(queueService); 72 | } 73 | 74 | @Test 75 | public void should_reload_correct_configs() throws Exception { 76 | Path configPath = write(""); 77 | 78 | QueueService queueService = mock(QueueService.class); 79 | QueueConfigsReader reader = spy(createReader(Collections.singletonList(configPath))); 80 | 81 | QueueConfigsReloader reloader = new QueueConfigsReloader(reader, queueService); 82 | reloader.start(); 83 | Files.write(configPath, ("q.testname.table=foo" + System.lineSeparator() + "q.testname.thread-count=1") 84 | .getBytes(StandardCharsets.UTF_8)); 85 | List queueConfigs = reader.parse(); 86 | Thread.sleep(1000); 87 | reloader.stop(); 88 | verify(reader, atLeast(2)).parse(); 89 | verify(queueService, atLeastOnce()).updateQueueConfigs(eq(queueConfigs)); 90 | } 91 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/settings/QueueIdTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import org.junit.Test; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 27.09.2017 9 | */ 10 | public class QueueIdTest { 11 | 12 | @Test 13 | public void should_define_correct_equals_hashcode() throws Exception { 14 | EqualsVerifier.forClass(QueueId.class).verify(); 15 | } 16 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/settings/QueueLocationTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.CoreMatchers.equalTo; 8 | 9 | /** 10 | * @author Oleg Kandaurov 11 | * @since 10.08.2017 12 | */ 13 | public class QueueLocationTest { 14 | 15 | @Test 16 | public void should_define_correct_equals_hashcode() throws Exception { 17 | EqualsVerifier.forClass(QueueLocation.class).verify(); 18 | } 19 | 20 | @Test 21 | public void should_filter_special_chars_in_table_name() { 22 | Assert.assertThat(QueueLocation.builder().withQueueId(new QueueId("1")) 23 | .withTableName(" t !@#$%^&*()._+-=1\n;'][{}").build().getTableName(), equalTo("t._1")); 24 | } 25 | 26 | @Test 27 | public void should_filter_special_chars_in_sequence_name() { 28 | Assert.assertThat(QueueLocation.builder().withQueueId(new QueueId("1")) 29 | .withTableName("1") 30 | .withIdSequence(" s !@#$%^&*()._+-=1\n;'][{}").build().getIdSequence().get(), equalTo("s._1")); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/settings/QueueSettingsTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.settings; 2 | 3 | import nl.jqno.equalsverifier.EqualsVerifier; 4 | import org.junit.Test; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 10.08.2017 9 | */ 10 | public class QueueSettingsTest { 11 | 12 | @Test 13 | public void should_define_correct_equals_hashcode() throws Exception { 14 | EqualsVerifier.forClass(QueueSettings.class).verify(); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/stub/FakeMillisTimeProvider.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.stub; 2 | 3 | import ru.yoomoney.tech.dbqueue.internal.processing.MillisTimeProvider; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author Oleg Kandaurov 9 | * @since 04.08.2017 10 | */ 11 | public class FakeMillisTimeProvider implements MillisTimeProvider { 12 | 13 | private final List times; 14 | private int invocationCount; 15 | 16 | public FakeMillisTimeProvider(List times) { 17 | this.times = times; 18 | } 19 | 20 | @Override 21 | public long getMillis() { 22 | Long currentTime = times.get(invocationCount); 23 | invocationCount++; 24 | return currentTime; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/stub/FakeQueueConsumer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.stub; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 4 | import ru.yoomoney.tech.dbqueue.api.Task; 5 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 6 | import ru.yoomoney.tech.dbqueue.api.TaskPayloadTransformer; 7 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 8 | 9 | import javax.annotation.Nonnull; 10 | import java.util.function.Function; 11 | 12 | /** 13 | * @author Oleg Kandaurov 14 | * @since 04.08.2017 15 | */ 16 | public class FakeQueueConsumer implements QueueConsumer { 17 | 18 | private final QueueConfig queueConfig; 19 | private final TaskPayloadTransformer transformer; 20 | private final Function, TaskExecutionResult> execFunc; 21 | 22 | public FakeQueueConsumer(QueueConfig queueConfig, TaskPayloadTransformer transformer, 23 | Function, TaskExecutionResult> execFunc) { 24 | this.queueConfig = queueConfig; 25 | this.transformer = transformer; 26 | this.execFunc = execFunc; 27 | } 28 | 29 | @Nonnull 30 | @Override 31 | public TaskExecutionResult execute(@Nonnull Task task) { 32 | return execFunc.apply(task); 33 | } 34 | 35 | @Nonnull 36 | @Override 37 | public QueueConfig getQueueConfig() { 38 | return queueConfig; 39 | } 40 | 41 | @Nonnull 42 | @Override 43 | public TaskPayloadTransformer getPayloadTransformer() { 44 | return transformer; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/stub/NoopQueueConsumer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.stub; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.Task; 4 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | /** 10 | * @author Oleg Kandaurov 11 | * @since 14.10.2019 12 | */ 13 | public class NoopQueueConsumer extends StringQueueConsumer { 14 | public NoopQueueConsumer(@Nonnull QueueConfig queueConfig) { 15 | super(queueConfig); 16 | } 17 | 18 | @Nonnull 19 | @Override 20 | public TaskExecutionResult execute(@Nonnull Task task) { 21 | return TaskExecutionResult.finish(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/stub/StringQueueConsumer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.stub; 2 | 3 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 4 | import ru.yoomoney.tech.dbqueue.api.TaskPayloadTransformer; 5 | import ru.yoomoney.tech.dbqueue.api.impl.NoopPayloadTransformer; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 7 | 8 | import javax.annotation.Nonnull; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | /** 13 | * Queue consumer without payload transformation 14 | * 15 | * @author Oleg Kandaurov 16 | * @since 02.10.2019 17 | */ 18 | public abstract class StringQueueConsumer implements QueueConsumer { 19 | 20 | @Nonnull 21 | private final QueueConfig queueConfig; 22 | 23 | public StringQueueConsumer(@Nonnull QueueConfig queueConfig) { 24 | this.queueConfig = requireNonNull(queueConfig); 25 | } 26 | 27 | @Nonnull 28 | @Override 29 | public QueueConfig getQueueConfig() { 30 | return queueConfig; 31 | } 32 | 33 | @Nonnull 34 | @Override 35 | public TaskPayloadTransformer getPayloadTransformer() { 36 | return NoopPayloadTransformer.getInstance(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/stub/StubDatabaseAccessLayer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.stub; 2 | 3 | import ru.yoomoney.tech.dbqueue.config.DatabaseAccessLayer; 4 | import ru.yoomoney.tech.dbqueue.config.DatabaseDialect; 5 | import ru.yoomoney.tech.dbqueue.config.QueueTableSchema; 6 | import ru.yoomoney.tech.dbqueue.dao.QueueDao; 7 | import ru.yoomoney.tech.dbqueue.dao.QueuePickTaskDao; 8 | import ru.yoomoney.tech.dbqueue.settings.FailureSettings; 9 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 10 | 11 | import javax.annotation.Nonnull; 12 | import java.util.function.Supplier; 13 | 14 | import static org.mockito.Mockito.mock; 15 | 16 | public class StubDatabaseAccessLayer implements DatabaseAccessLayer { 17 | 18 | private final QueueDao queueDao; 19 | 20 | public StubDatabaseAccessLayer() { 21 | this.queueDao = mock(QueueDao.class); 22 | } 23 | 24 | public StubDatabaseAccessLayer(QueueDao queueDao) { 25 | this.queueDao = queueDao; 26 | } 27 | 28 | @Override 29 | @Nonnull 30 | public QueueDao getQueueDao() { 31 | return queueDao; 32 | } 33 | 34 | @Override 35 | @Nonnull 36 | public QueuePickTaskDao createQueuePickTaskDao(@Nonnull QueueLocation queueLocation, 37 | @Nonnull FailureSettings failureSettings) { 38 | return mock(QueuePickTaskDao.class); 39 | } 40 | 41 | @Override 42 | public T transact(@Nonnull Supplier supplier) { 43 | return supplier.get(); 44 | } 45 | 46 | @Override 47 | public void transact(@Nonnull Runnable runnable) { 48 | runnable.run(); 49 | } 50 | 51 | @Nonnull 52 | @Override 53 | public DatabaseDialect getDatabaseDialect() { 54 | return DatabaseDialect.POSTGRESQL; 55 | } 56 | 57 | @Nonnull 58 | @Override 59 | public QueueTableSchema getQueueTableSchema() { 60 | return QueueTableSchema.builder().build(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /db-queue-core/src/test/java/ru/yoomoney/tech/dbqueue/stub/TestFixtures.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.stub; 2 | 3 | import ru.yoomoney.tech.dbqueue.settings.ExtSettings; 4 | import ru.yoomoney.tech.dbqueue.settings.FailRetryType; 5 | import ru.yoomoney.tech.dbqueue.settings.FailureSettings; 6 | import ru.yoomoney.tech.dbqueue.settings.PollSettings; 7 | import ru.yoomoney.tech.dbqueue.settings.ProcessingMode; 8 | import ru.yoomoney.tech.dbqueue.settings.ProcessingSettings; 9 | import ru.yoomoney.tech.dbqueue.settings.QueueSettings; 10 | import ru.yoomoney.tech.dbqueue.settings.ReenqueueRetryType; 11 | import ru.yoomoney.tech.dbqueue.settings.ReenqueueSettings; 12 | 13 | import java.time.Duration; 14 | import java.util.HashMap; 15 | 16 | public class TestFixtures { 17 | 18 | public static QueueSettings.Builder createQueueSettings() { 19 | return QueueSettings.builder() 20 | .withProcessingSettings(createProcessingSettings().build()) 21 | .withPollSettings(createPollSettings().build()) 22 | .withFailureSettings(createFailureSettings().build()) 23 | .withReenqueueSettings(createReenqueueSettings().build()) 24 | .withExtSettings(ExtSettings.builder().withSettings(new HashMap<>()).build()); 25 | } 26 | 27 | public static ProcessingSettings.Builder createProcessingSettings() { 28 | return ProcessingSettings.builder() 29 | .withProcessingMode(ProcessingMode.SEPARATE_TRANSACTIONS) 30 | .withThreadCount(1); 31 | } 32 | 33 | public static PollSettings.Builder createPollSettings() { 34 | return PollSettings.builder() 35 | .withBetweenTaskTimeout(Duration.ofMillis(0)) 36 | .withNoTaskTimeout(Duration.ofMillis(0)) 37 | .withFatalCrashTimeout(Duration.ofSeconds(0)); 38 | } 39 | 40 | public static FailureSettings.Builder createFailureSettings() { 41 | return FailureSettings.builder() 42 | .withRetryType(FailRetryType.GEOMETRIC_BACKOFF) 43 | .withRetryInterval(Duration.ofMinutes(1)); 44 | } 45 | 46 | public static ReenqueueSettings.Builder createReenqueueSettings() { 47 | return ReenqueueSettings.builder() 48 | .withRetryType(ReenqueueRetryType.MANUAL); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /db-queue-core/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | %-5p [%c{1}] %m%n%ex 11 | 12 | 13 | 14 | 15 | %-5p [%c{1}] %m%n%ex 16 | 17 | 18 | 19 | 20 | %-5p [%c{1}] %m%n%ex 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /db-queue-core/static-analysis.properties: -------------------------------------------------------------------------------- 1 | checkstyle=1 -------------------------------------------------------------------------------- /db-queue-spring/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: "$rootProject.projectDir/project.gradle", to: buildscript 3 | } 4 | 5 | dependencies { 6 | 7 | compile project(':db-queue-core') 8 | 9 | def springVersion = '5.3.16' 10 | compile "org.springframework:spring-jdbc:${springVersion}", 11 | "org.springframework:spring-tx:${springVersion}" 12 | 13 | compileOnly 'com.google.code.findbugs:jsr305:3.0.1', 14 | 'com.google.code.findbugs:annotations:3.0.1' 15 | 16 | testCompile 'junit:junit:4.13.2', 17 | 'org.apache.logging.log4j:log4j-core:2.17.1', 18 | 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1', 19 | 'org.mockito:mockito-core:4.0.0', 20 | 'nl.jqno.equalsverifier:equalsverifier:3.9', 21 | 22 | 'org.testcontainers:testcontainers:1.16.3', 23 | 'org.testcontainers:postgresql:1.16.3', 24 | 'org.testcontainers:mssqlserver:1.16.3', 25 | 26 | 'org.postgresql:postgresql:42.3.3', 27 | 28 | 'com.microsoft.sqlserver:mssql-jdbc:8.2.0.jre8', 29 | 30 | 'org.testcontainers:oracle-xe:1.16.0', 31 | 'com.oracle.ojdbc:ojdbc8:19.3.0.0', 32 | 33 | 'com.h2database:h2:1.4.200' 34 | 35 | 36 | testCompileOnly 'com.google.code.findbugs:jsr305:3.0.1', 37 | 'com.google.code.findbugs:annotations:3.0.1' 38 | } 39 | -------------------------------------------------------------------------------- /db-queue-spring/coverage.properties: -------------------------------------------------------------------------------- 1 | instruction=65 2 | branch=60 3 | method=65 4 | class=62 5 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/CustomH2PickTaskDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.H2DatabaseInitializer; 5 | 6 | public class CustomH2PickTaskDaoTest extends QueuePickTaskDaoTest { 7 | 8 | @BeforeClass 9 | public static void beforeClass() { 10 | H2DatabaseInitializer.initialize(); 11 | } 12 | 13 | public CustomH2PickTaskDaoTest() { 14 | super( 15 | new H2QueueDao( 16 | H2DatabaseInitializer.getJdbcTemplate(), 17 | H2DatabaseInitializer.CUSTOM_SCHEMA), 18 | (queueLocation, failureSettings) -> 19 | new H2QueuePickTaskDao( 20 | H2DatabaseInitializer.getJdbcTemplate(), 21 | H2DatabaseInitializer.CUSTOM_SCHEMA, 22 | queueLocation, 23 | failureSettings), 24 | H2DatabaseInitializer.CUSTOM_TABLE_NAME, 25 | H2DatabaseInitializer.CUSTOM_SCHEMA, 26 | H2DatabaseInitializer.getJdbcTemplate(), 27 | H2DatabaseInitializer.getTransactionTemplate()); 28 | } 29 | 30 | @Override 31 | protected String currentTimeSql() { 32 | return "now()"; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/CustomH2QueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.H2DatabaseInitializer; 5 | 6 | public class CustomH2QueueDaoTest extends QueueDaoTest { 7 | 8 | @BeforeClass 9 | public static void beforeClass() { 10 | H2DatabaseInitializer.initialize(); 11 | } 12 | 13 | public CustomH2QueueDaoTest() { 14 | super( 15 | new H2QueueDao(H2DatabaseInitializer.getJdbcTemplate(), H2DatabaseInitializer.CUSTOM_SCHEMA), 16 | H2DatabaseInitializer.CUSTOM_TABLE_NAME, 17 | H2DatabaseInitializer.CUSTOM_SCHEMA, 18 | H2DatabaseInitializer.getJdbcTemplate(), 19 | H2DatabaseInitializer.getTransactionTemplate()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/CustomMssqlQueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.MssqlDatabaseInitializer; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @author Behrooz Shabani 9 | * @since 25.01.2020 10 | */ 11 | public class CustomMssqlQueueDaoTest extends QueueDaoTest { 12 | 13 | @BeforeClass 14 | public static void beforeClass() { 15 | MssqlDatabaseInitializer.initialize(); 16 | } 17 | 18 | public CustomMssqlQueueDaoTest() { 19 | super(new MssqlQueueDao(MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.CUSTOM_SCHEMA), 20 | MssqlDatabaseInitializer.CUSTOM_TABLE_NAME, MssqlDatabaseInitializer.CUSTOM_SCHEMA, 21 | MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.getTransactionTemplate()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/CustomMssqlQueuePickTaskDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.MssqlDatabaseInitializer; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @author Behrooz Shabani 9 | * @since 25.01.2020 10 | */ 11 | public class CustomMssqlQueuePickTaskDaoTest extends QueuePickTaskDaoTest { 12 | 13 | @BeforeClass 14 | public static void beforeClass() { 15 | MssqlDatabaseInitializer.initialize(); 16 | } 17 | 18 | public CustomMssqlQueuePickTaskDaoTest() { 19 | super(new MssqlQueueDao(MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.CUSTOM_SCHEMA), 20 | (queueLocation, failureSettings) -> new MssqlQueuePickTaskDao(MssqlDatabaseInitializer.getJdbcTemplate(), 21 | MssqlDatabaseInitializer.CUSTOM_SCHEMA, queueLocation, failureSettings), 22 | MssqlDatabaseInitializer.CUSTOM_TABLE_NAME, MssqlDatabaseInitializer.CUSTOM_SCHEMA, 23 | MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.getTransactionTemplate()); 24 | } 25 | 26 | @Override 27 | protected String currentTimeSql() { 28 | return "SYSDATETIMEOFFSET()"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/CustomOracle11QueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import org.junit.Ignore; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 7 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.OracleDatabaseInitializer; 8 | 9 | import java.util.UUID; 10 | 11 | /** 12 | * @author Oleg Kandaurov 13 | * @since 12.10.2019 14 | */ 15 | @Ignore("https://github.com/yoomoney/db-queue/issues/10") 16 | public class CustomOracle11QueueDaoTest extends QueueDaoTest { 17 | 18 | @BeforeClass 19 | public static void beforeClass() { 20 | OracleDatabaseInitializer.initialize(); 21 | } 22 | 23 | public CustomOracle11QueueDaoTest() { 24 | super(new Oracle11QueueDao(OracleDatabaseInitializer.getJdbcTemplate(), OracleDatabaseInitializer.CUSTOM_SCHEMA), 25 | OracleDatabaseInitializer.CUSTOM_TABLE_NAME, OracleDatabaseInitializer.CUSTOM_SCHEMA, 26 | OracleDatabaseInitializer.getJdbcTemplate(), OracleDatabaseInitializer.getTransactionTemplate()); 27 | } 28 | 29 | @Override 30 | protected QueueLocation generateUniqueLocation() { 31 | return QueueLocation.builder().withTableName(tableName) 32 | .withIdSequence("tasks_seq") 33 | .withQueueId(new QueueId("test-queue-" + UUID.randomUUID())).build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/CustomOracle11QueuePickTaskDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import org.junit.Ignore; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 7 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.OracleDatabaseInitializer; 8 | 9 | import java.util.UUID; 10 | 11 | /** 12 | * @author Oleg Kandaurov 13 | * @since 12.10.2019 14 | */ 15 | @Ignore("https://github.com/yoomoney/db-queue/issues/10") 16 | public class CustomOracle11QueuePickTaskDaoTest extends QueuePickTaskDaoTest { 17 | 18 | @BeforeClass 19 | public static void beforeClass() { 20 | OracleDatabaseInitializer.initialize(); 21 | } 22 | 23 | public CustomOracle11QueuePickTaskDaoTest() { 24 | super(new Oracle11QueueDao(OracleDatabaseInitializer.getJdbcTemplate(), OracleDatabaseInitializer.CUSTOM_SCHEMA), 25 | (queueLocation, failureSettings) -> new Oracle11QueuePickTaskDao(OracleDatabaseInitializer.getJdbcTemplate(), 26 | OracleDatabaseInitializer.CUSTOM_SCHEMA, queueLocation, failureSettings), 27 | OracleDatabaseInitializer.CUSTOM_TABLE_NAME, OracleDatabaseInitializer.CUSTOM_SCHEMA, 28 | OracleDatabaseInitializer.getJdbcTemplate(), OracleDatabaseInitializer.getTransactionTemplate()); 29 | } 30 | 31 | @Override 32 | protected String currentTimeSql() { 33 | return "CURRENT_TIMESTAMP"; 34 | } 35 | 36 | @Override 37 | protected QueueLocation generateUniqueLocation() { 38 | return QueueLocation.builder().withTableName(tableName) 39 | .withIdSequence("tasks_seq") 40 | .withQueueId(new QueueId("test-queue-" + UUID.randomUUID())).build(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/CustomPostgresQueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.PostgresDatabaseInitializer; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 12.10.2019 9 | */ 10 | public class CustomPostgresQueueDaoTest extends QueueDaoTest { 11 | 12 | @BeforeClass 13 | public static void beforeClass() { 14 | PostgresDatabaseInitializer.initialize(); 15 | } 16 | 17 | public CustomPostgresQueueDaoTest() { 18 | super(new PostgresQueueDao(PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.CUSTOM_SCHEMA), 19 | PostgresDatabaseInitializer.CUSTOM_TABLE_NAME, PostgresDatabaseInitializer.CUSTOM_SCHEMA, 20 | PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.getTransactionTemplate()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/CustomPostgresQueuePickTaskDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.PostgresDatabaseInitializer; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 12.10.2019 9 | */ 10 | public class CustomPostgresQueuePickTaskDaoTest extends QueuePickTaskDaoTest { 11 | 12 | @BeforeClass 13 | public static void beforeClass() { 14 | PostgresDatabaseInitializer.initialize(); 15 | } 16 | 17 | public CustomPostgresQueuePickTaskDaoTest() { 18 | super(new PostgresQueueDao(PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.CUSTOM_SCHEMA), 19 | (queueLocation, failureSettings) -> new PostgresQueuePickTaskDao(PostgresDatabaseInitializer.getJdbcTemplate(), 20 | PostgresDatabaseInitializer.CUSTOM_SCHEMA, queueLocation, failureSettings), 21 | PostgresDatabaseInitializer.CUSTOM_TABLE_NAME, PostgresDatabaseInitializer.CUSTOM_SCHEMA, 22 | PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.getTransactionTemplate()); 23 | } 24 | 25 | @Override 26 | protected String currentTimeSql() { 27 | return "now()"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultH2QueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.H2DatabaseInitializer; 5 | 6 | public class DefaultH2QueueDaoTest extends QueueDaoTest { 7 | 8 | @BeforeClass 9 | public static void beforeClass() { 10 | H2DatabaseInitializer.initialize(); 11 | } 12 | 13 | public DefaultH2QueueDaoTest() { 14 | super( 15 | new H2QueueDao(H2DatabaseInitializer.getJdbcTemplate(), H2DatabaseInitializer.DEFAULT_SCHEMA), 16 | H2DatabaseInitializer.DEFAULT_TABLE_NAME, 17 | H2DatabaseInitializer.DEFAULT_SCHEMA, 18 | H2DatabaseInitializer.getJdbcTemplate(), 19 | H2DatabaseInitializer.getTransactionTemplate()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultH2QueuePickTaskDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.H2DatabaseInitializer; 5 | 6 | public class DefaultH2QueuePickTaskDaoTest extends QueuePickTaskDaoTest { 7 | 8 | @BeforeClass 9 | public static void beforeClass() { 10 | H2DatabaseInitializer.initialize(); 11 | } 12 | 13 | public DefaultH2QueuePickTaskDaoTest() { 14 | super( 15 | new H2QueueDao(H2DatabaseInitializer.getJdbcTemplate(), H2DatabaseInitializer.DEFAULT_SCHEMA), 16 | (queueLocation, failureSettings) -> 17 | new H2QueuePickTaskDao( 18 | H2DatabaseInitializer.getJdbcTemplate(), 19 | H2DatabaseInitializer.DEFAULT_SCHEMA, 20 | queueLocation, failureSettings), 21 | H2DatabaseInitializer.DEFAULT_TABLE_NAME, H2DatabaseInitializer.DEFAULT_SCHEMA, 22 | H2DatabaseInitializer.getJdbcTemplate(), H2DatabaseInitializer.getTransactionTemplate()); 23 | } 24 | 25 | @Override 26 | protected String currentTimeSql() { 27 | return "now()"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultH2WithSequenceQueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 6 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.H2DatabaseInitializer; 7 | 8 | import java.util.UUID; 9 | 10 | 11 | public class DefaultH2WithSequenceQueueDaoTest extends QueueDaoTest { 12 | 13 | @BeforeClass 14 | public static void beforeClass() { 15 | H2DatabaseInitializer.initialize(); 16 | } 17 | 18 | public DefaultH2WithSequenceQueueDaoTest() { 19 | super( 20 | new H2QueueDao( 21 | H2DatabaseInitializer.getJdbcTemplate(), 22 | H2DatabaseInitializer.DEFAULT_SCHEMA), 23 | H2DatabaseInitializer.DEFAULT_TABLE_NAME_WO_INC, 24 | H2DatabaseInitializer.DEFAULT_SCHEMA, 25 | H2DatabaseInitializer.getJdbcTemplate(), 26 | H2DatabaseInitializer.getTransactionTemplate()); 27 | } 28 | 29 | protected QueueLocation generateUniqueLocation() { 30 | return QueueLocation.builder().withTableName(tableName) 31 | .withQueueId(new QueueId("test-queue-" + UUID.randomUUID())) 32 | .withIdSequence("tasks_seq").build(); 33 | } 34 | } -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultMssqlQueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.MssqlDatabaseInitializer; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @author Behrooz Shabani 9 | * @since 25.01.2020 10 | */ 11 | public class DefaultMssqlQueueDaoTest extends QueueDaoTest { 12 | 13 | @BeforeClass 14 | public static void beforeClass() { 15 | MssqlDatabaseInitializer.initialize(); 16 | } 17 | 18 | public DefaultMssqlQueueDaoTest() { 19 | super(new MssqlQueueDao(MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.DEFAULT_SCHEMA), 20 | MssqlDatabaseInitializer.DEFAULT_TABLE_NAME, MssqlDatabaseInitializer.DEFAULT_SCHEMA, 21 | MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.getTransactionTemplate()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultMssqlQueuePickTaskDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.MssqlDatabaseInitializer; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @author Behrooz Shabani 9 | * @since 25.01.2020 10 | */ 11 | public class DefaultMssqlQueuePickTaskDaoTest extends QueuePickTaskDaoTest { 12 | 13 | @BeforeClass 14 | public static void beforeClass() { 15 | MssqlDatabaseInitializer.initialize(); 16 | } 17 | 18 | public DefaultMssqlQueuePickTaskDaoTest() { 19 | super(new MssqlQueueDao(MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.DEFAULT_SCHEMA), 20 | (queueLocation, failureSettings) -> new MssqlQueuePickTaskDao(MssqlDatabaseInitializer.getJdbcTemplate(), 21 | MssqlDatabaseInitializer.DEFAULT_SCHEMA, queueLocation, failureSettings), 22 | MssqlDatabaseInitializer.DEFAULT_TABLE_NAME, MssqlDatabaseInitializer.DEFAULT_SCHEMA, 23 | MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.getTransactionTemplate()); 24 | } 25 | 26 | @Override 27 | protected String currentTimeSql() { 28 | return "SYSDATETIMEOFFSET()"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultMssqlWithSequenceQueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 6 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.MssqlDatabaseInitializer; 7 | 8 | import java.util.UUID; 9 | 10 | /** 11 | * @author Oleg Kandaurov 12 | * @author Behrooz Shabani 13 | * @since 25.01.2020 14 | */ 15 | public class DefaultMssqlWithSequenceQueueDaoTest extends QueueDaoTest { 16 | 17 | @BeforeClass 18 | public static void beforeClass() { 19 | MssqlDatabaseInitializer.initialize(); 20 | } 21 | 22 | public DefaultMssqlWithSequenceQueueDaoTest() { 23 | super(new MssqlQueueDao(MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.DEFAULT_SCHEMA), 24 | MssqlDatabaseInitializer.DEFAULT_TABLE_NAME_WO_IDENT, MssqlDatabaseInitializer.DEFAULT_SCHEMA, 25 | MssqlDatabaseInitializer.getJdbcTemplate(), MssqlDatabaseInitializer.getTransactionTemplate()); 26 | } 27 | 28 | @Override 29 | protected QueueLocation generateUniqueLocation() { 30 | return QueueLocation.builder().withTableName(tableName) 31 | .withQueueId(new QueueId("test-queue-" + UUID.randomUUID())) 32 | .withIdSequence("tasks_seq").build(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultOracle11QueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import org.junit.Ignore; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 7 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.OracleDatabaseInitializer; 8 | 9 | import java.util.UUID; 10 | 11 | /** 12 | * @author Oleg Kandaurov 13 | * @since 15.05.2020 14 | */ 15 | @Ignore("https://github.com/yoomoney/db-queue/issues/10") 16 | public class DefaultOracle11QueueDaoTest extends QueueDaoTest { 17 | 18 | @BeforeClass 19 | public static void beforeClass() { 20 | OracleDatabaseInitializer.initialize(); 21 | } 22 | 23 | public DefaultOracle11QueueDaoTest() { 24 | super(new Oracle11QueueDao(OracleDatabaseInitializer.getJdbcTemplate(), OracleDatabaseInitializer.DEFAULT_SCHEMA), 25 | OracleDatabaseInitializer.DEFAULT_TABLE_NAME, OracleDatabaseInitializer.DEFAULT_SCHEMA, 26 | OracleDatabaseInitializer.getJdbcTemplate(), OracleDatabaseInitializer.getTransactionTemplate()); 27 | } 28 | 29 | @Override 30 | protected QueueLocation generateUniqueLocation() { 31 | return QueueLocation.builder().withTableName(tableName) 32 | .withIdSequence("tasks_seq") 33 | .withQueueId(new QueueId("test-queue-" + UUID.randomUUID())).build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultOracle11QueuePickTaskDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import org.junit.Ignore; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 6 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 7 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.OracleDatabaseInitializer; 8 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.PostgresDatabaseInitializer; 9 | 10 | import java.util.UUID; 11 | 12 | /** 13 | * @author Oleg Kandaurov 14 | * @since 12.10.2019 15 | */ 16 | @Ignore("https://github.com/yoomoney/db-queue/issues/10") 17 | public class DefaultOracle11QueuePickTaskDaoTest extends QueuePickTaskDaoTest { 18 | 19 | @BeforeClass 20 | public static void beforeClass() { 21 | OracleDatabaseInitializer.initialize(); 22 | } 23 | 24 | public DefaultOracle11QueuePickTaskDaoTest() { 25 | super(new Oracle11QueueDao(OracleDatabaseInitializer.getJdbcTemplate(), OracleDatabaseInitializer.DEFAULT_SCHEMA), 26 | (queueLocation, failureSettings) -> new Oracle11QueuePickTaskDao(OracleDatabaseInitializer.getJdbcTemplate(), 27 | PostgresDatabaseInitializer.DEFAULT_SCHEMA, queueLocation, failureSettings), 28 | OracleDatabaseInitializer.DEFAULT_TABLE_NAME, OracleDatabaseInitializer.DEFAULT_SCHEMA, 29 | OracleDatabaseInitializer.getJdbcTemplate(), OracleDatabaseInitializer.getTransactionTemplate()); 30 | } 31 | 32 | @Override 33 | protected String currentTimeSql() { 34 | return "CURRENT_TIMESTAMP"; 35 | } 36 | 37 | @Override 38 | protected QueueLocation generateUniqueLocation() { 39 | return QueueLocation.builder().withTableName(tableName) 40 | .withIdSequence("tasks_seq") 41 | .withQueueId(new QueueId("test-queue-" + UUID.randomUUID())).build(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultPostgresQueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.PostgresDatabaseInitializer; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 12.10.2019 9 | */ 10 | public class DefaultPostgresQueueDaoTest extends QueueDaoTest { 11 | 12 | @BeforeClass 13 | public static void beforeClass() { 14 | PostgresDatabaseInitializer.initialize(); 15 | } 16 | 17 | public DefaultPostgresQueueDaoTest() { 18 | super(new PostgresQueueDao(PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.DEFAULT_SCHEMA), 19 | PostgresDatabaseInitializer.DEFAULT_TABLE_NAME, PostgresDatabaseInitializer.DEFAULT_SCHEMA, 20 | PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.getTransactionTemplate()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultPostgresQueuePickTaskDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.PostgresDatabaseInitializer; 5 | 6 | /** 7 | * @author Oleg Kandaurov 8 | * @since 12.10.2019 9 | */ 10 | public class DefaultPostgresQueuePickTaskDaoTest extends QueuePickTaskDaoTest { 11 | 12 | @BeforeClass 13 | public static void beforeClass() { 14 | PostgresDatabaseInitializer.initialize(); 15 | } 16 | 17 | public DefaultPostgresQueuePickTaskDaoTest() { 18 | super(new PostgresQueueDao(PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.DEFAULT_SCHEMA), 19 | (queueLocation, failureSettings) -> new PostgresQueuePickTaskDao(PostgresDatabaseInitializer.getJdbcTemplate(), 20 | PostgresDatabaseInitializer.DEFAULT_SCHEMA, queueLocation, failureSettings), 21 | PostgresDatabaseInitializer.DEFAULT_TABLE_NAME, PostgresDatabaseInitializer.DEFAULT_SCHEMA, 22 | PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.getTransactionTemplate()); 23 | } 24 | 25 | @Override 26 | protected String currentTimeSql() { 27 | return "now()"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/java/ru/yoomoney/tech/dbqueue/spring/dao/DefaultPostgresWithSequenceQueueDaoTest.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.spring.dao; 2 | 3 | import org.junit.BeforeClass; 4 | import ru.yoomoney.tech.dbqueue.settings.QueueId; 5 | import ru.yoomoney.tech.dbqueue.settings.QueueLocation; 6 | import ru.yoomoney.tech.dbqueue.spring.dao.utils.PostgresDatabaseInitializer; 7 | 8 | import java.util.UUID; 9 | 10 | /** 11 | * @author Oleg Kandaurov 12 | * @since 12.10.2019 13 | */ 14 | public class DefaultPostgresWithSequenceQueueDaoTest extends QueueDaoTest { 15 | 16 | @BeforeClass 17 | public static void beforeClass() { 18 | PostgresDatabaseInitializer.initialize(); 19 | } 20 | 21 | public DefaultPostgresWithSequenceQueueDaoTest() { 22 | super(new PostgresQueueDao(PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.DEFAULT_SCHEMA), 23 | PostgresDatabaseInitializer.DEFAULT_TABLE_NAME_WO_INC, PostgresDatabaseInitializer.DEFAULT_SCHEMA, 24 | PostgresDatabaseInitializer.getJdbcTemplate(), PostgresDatabaseInitializer.getTransactionTemplate()); 25 | } 26 | 27 | protected QueueLocation generateUniqueLocation() { 28 | return QueueLocation.builder().withTableName(tableName) 29 | .withQueueId(new QueueId("test-queue-" + UUID.randomUUID())) 30 | .withIdSequence("tasks_seq").build(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/resources/container-license-acceptance.txt: -------------------------------------------------------------------------------- 1 | mcr.microsoft.com/mssql/server:2019-CU1-ubuntu-16.04 2 | -------------------------------------------------------------------------------- /db-queue-spring/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /db-queue-spring/static-analysis.properties: -------------------------------------------------------------------------------- 1 | checkstyle=0 2 | -------------------------------------------------------------------------------- /db-queue-test/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: "$rootProject.projectDir/project.gradle", to: buildscript 3 | } 4 | 5 | dependencies { 6 | 7 | 8 | testCompile project(':db-queue-core'), 9 | project(':db-queue-spring'), 10 | project(':db-queue-brave'), 11 | 12 | 'io.zipkin.brave:brave-context-log4j2:5.10.2', 13 | 14 | 'junit:junit:4.13.2', 15 | 16 | 'org.apache.logging.log4j:log4j-core:2.17.1', 17 | 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1', 18 | 19 | 'org.testcontainers:testcontainers:1.16.3', 20 | 'org.testcontainers:postgresql:1.16.3', 21 | 'org.postgresql:postgresql:42.3.3' 22 | 23 | 24 | compileOnly 'com.google.code.findbugs:jsr305:3.0.1', 25 | 'com.google.code.findbugs:annotations:3.0.1' 26 | } 27 | -------------------------------------------------------------------------------- /db-queue-test/src/test/java/ru/yoomoney/tech/dbqueue/test/DefaultDatabaseInitializer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.test; 2 | 3 | import org.postgresql.ds.PGSimpleDataSource; 4 | import org.springframework.jdbc.core.JdbcTemplate; 5 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 6 | import org.springframework.transaction.TransactionDefinition; 7 | import org.springframework.transaction.support.TransactionTemplate; 8 | import org.testcontainers.containers.PostgreSQLContainer; 9 | import org.testcontainers.utility.TestcontainersConfiguration; 10 | 11 | import java.util.Optional; 12 | 13 | /** 14 | * @author Oleg Kandaurov 15 | * @since 11.06.2021 16 | */ 17 | public class DefaultDatabaseInitializer { 18 | 19 | private static JdbcTemplate pgJdbcTemplate; 20 | private static TransactionTemplate pgTransactionTemplate; 21 | 22 | public static synchronized void initialize() { 23 | if (pgJdbcTemplate != null) { 24 | return; 25 | } 26 | 27 | String ryukImage = Optional.ofNullable(System.getProperty("testcontainers.ryuk.container.image")) 28 | .orElse("quay.io/testcontainers/ryuk:0.2.3"); 29 | TestcontainersConfiguration.getInstance() 30 | .updateGlobalConfig("ryuk.container.image", ryukImage); 31 | 32 | String postgresImage = Optional.ofNullable(System.getProperty("testcontainers.postgresql.container.image")) 33 | .orElse("postgres:9.5"); 34 | PostgreSQLContainer dbContainer = new PostgreSQLContainer<>(postgresImage); 35 | dbContainer.withEnv("POSTGRES_INITDB_ARGS", "--nosync"); 36 | dbContainer.withCommand("postgres -c fsync=off -c full_page_writes=off -c synchronous_commit=off"); 37 | dbContainer.start(); 38 | PGSimpleDataSource dataSource = new PGSimpleDataSource(); 39 | dataSource.setUrl(dbContainer.getJdbcUrl()); 40 | dataSource.setPassword(dbContainer.getPassword()); 41 | dataSource.setUser(dbContainer.getUsername()); 42 | pgJdbcTemplate = new JdbcTemplate(dataSource); 43 | pgTransactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource)); 44 | pgTransactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); 45 | pgTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); 46 | 47 | } 48 | 49 | public static void createTable(String ddlTemplate, String tableName) { 50 | initialize(); 51 | executeDdl(String.format(ddlTemplate, tableName, tableName, tableName)); 52 | } 53 | 54 | private static void executeDdl(String ddl) { 55 | initialize(); 56 | getTransactionTemplate().execute(status -> { 57 | getJdbcTemplate().execute(ddl); 58 | return new Object(); 59 | }); 60 | } 61 | 62 | public static JdbcTemplate getJdbcTemplate() { 63 | initialize(); 64 | return pgJdbcTemplate; 65 | } 66 | 67 | public static TransactionTemplate getTransactionTemplate() { 68 | initialize(); 69 | return pgTransactionTemplate; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /db-queue-test/src/test/java/ru/yoomoney/tech/dbqueue/test/StringQueueConsumer.java: -------------------------------------------------------------------------------- 1 | package ru.yoomoney.tech.dbqueue.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import ru.yoomoney.tech.dbqueue.api.QueueConsumer; 6 | import ru.yoomoney.tech.dbqueue.api.Task; 7 | import ru.yoomoney.tech.dbqueue.api.TaskExecutionResult; 8 | import ru.yoomoney.tech.dbqueue.api.TaskPayloadTransformer; 9 | import ru.yoomoney.tech.dbqueue.api.impl.NoopPayloadTransformer; 10 | import ru.yoomoney.tech.dbqueue.settings.QueueConfig; 11 | 12 | import javax.annotation.Nonnull; 13 | import java.util.concurrent.atomic.AtomicBoolean; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | import static java.util.Objects.requireNonNull; 17 | 18 | /** 19 | * Queue consumer without payload transformation 20 | * 21 | * @author Oleg Kandaurov 22 | * @since 11.06.2021 23 | */ 24 | public class StringQueueConsumer implements QueueConsumer { 25 | 26 | private static final Logger log = LoggerFactory.getLogger(StringQueueConsumer.class); 27 | 28 | @Nonnull 29 | private final QueueConfig queueConfig; 30 | @Nonnull 31 | private final AtomicInteger taskConsumedCount; 32 | 33 | public StringQueueConsumer(@Nonnull QueueConfig queueConfig, 34 | @Nonnull AtomicInteger taskConsumedCount) { 35 | this.queueConfig = requireNonNull(queueConfig); 36 | this.taskConsumedCount = requireNonNull(taskConsumedCount); 37 | } 38 | 39 | @Nonnull 40 | @Override 41 | public TaskExecutionResult execute(@Nonnull Task task) { 42 | log.info("payload={}", task.getPayloadOrThrow()); 43 | taskConsumedCount.incrementAndGet(); 44 | return TaskExecutionResult.finish(); 45 | } 46 | 47 | @Nonnull 48 | @Override 49 | public QueueConfig getQueueConfig() { 50 | return queueConfig; 51 | } 52 | 53 | @Nonnull 54 | @Override 55 | public TaskPayloadTransformer getPayloadTransformer() { 56 | return NoopPayloadTransformer.getInstance(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /db-queue-test/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=15.1.1-SNAPSHOT 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoomoney/db-queue/72449209a98191bb8b0c58cc2ee0376d0747f3aa/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /project.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | mavenCentral() 3 | maven { url 'https://plugins.gradle.org/m2/' } 4 | 5 | dependencies { 6 | classpath 'ru.yoomoney.gradle.plugins:library-project-plugin:7.+' 7 | } 8 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'db-queue-core', 2 | 'db-queue-spring', 3 | 'db-queue-brave', 4 | 'db-queue-test' 5 | 6 | --------------------------------------------------------------------------------