Skip to content

Commit d2a06fc

Browse files
authored
Slot supplier interface & fixed-size implementation (#2014)
https://github.com/temporalio/proposals/blob/master/all-sdk/autotuning.md
1 parent 3568970 commit d2a06fc

35 files changed

+1640
-262
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ jobs:
3333
USE_DOCKER_SERVICE: false
3434
run: ./gradlew --no-daemon test -x checkLicenseMain -x checkLicenses -x spotlessCheck -x spotlessApply -x spotlessJava -P edgeDepsTest
3535

36+
- name: Publish Test Report
37+
uses: mikepenz/action-junit-report@v4
38+
if: success() || failure() # always run even if the previous step fails
39+
with:
40+
report_paths: '**/build/test-results/test/TEST-*.xml'
41+
3642
unit_test_jdk8:
3743
name: Unit test with docker service [JDK8]
3844
runs-on: ubuntu-latest
@@ -54,7 +60,6 @@ jobs:
5460
- name: Set up Gradle
5561
uses: gradle/actions/setup-gradle@v3
5662

57-
5863
- name: Start containerized server and dependencies
5964
run: |
6065
docker compose \
@@ -68,6 +73,12 @@ jobs:
6873
USE_DOCKER_SERVICE: true
6974
run: ./gradlew --no-daemon test -x checkLicenseMain -x checkLicenses -x spotlessCheck -x spotlessApply -x spotlessJava
7075

76+
- name: Publish Test Report
77+
uses: mikepenz/action-junit-report@v4
78+
if: success() || failure() # always run even if the previous step fails
79+
with:
80+
report_paths: '**/build/test-results/test/TEST-*.xml'
81+
7182
copyright:
7283
name: Copyright and code format
7384
runs-on: ubuntu-latest
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3+
*
4+
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this material except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package io.temporal.internal.activity;
22+
23+
import io.temporal.activity.ActivityInfo;
24+
import io.temporal.api.workflowservice.v1.PollActivityTaskQueueResponseOrBuilder;
25+
26+
public class ActivityPollResponseToInfo {
27+
public static ActivityInfo toActivityInfoImpl(
28+
PollActivityTaskQueueResponseOrBuilder response,
29+
String namespace,
30+
String activityTaskQueue,
31+
boolean local) {
32+
return new ActivityInfoImpl(response, namespace, activityTaskQueue, local, null);
33+
}
34+
}

temporal-sdk/src/main/java/io/temporal/internal/worker/ActivityPollTask.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@
3030
import io.temporal.api.workflowservice.v1.GetSystemInfoResponse;
3131
import io.temporal.api.workflowservice.v1.PollActivityTaskQueueRequest;
3232
import io.temporal.api.workflowservice.v1.PollActivityTaskQueueResponse;
33+
import io.temporal.internal.activity.ActivityPollResponseToInfo;
3334
import io.temporal.internal.common.ProtobufTimeUtils;
3435
import io.temporal.serviceclient.WorkflowServiceStubs;
3536
import io.temporal.worker.MetricsType;
37+
import io.temporal.worker.tuning.*;
3638
import java.util.Objects;
37-
import java.util.concurrent.Semaphore;
3839
import java.util.function.Supplier;
3940
import javax.annotation.Nonnull;
4041
import javax.annotation.Nullable;
@@ -45,7 +46,7 @@ final class ActivityPollTask implements Poller.PollTask<ActivityTask> {
4546
private static final Logger log = LoggerFactory.getLogger(ActivityPollTask.class);
4647

4748
private final WorkflowServiceStubs service;
48-
private final Semaphore pollSemaphore;
49+
private final TrackingSlotSupplier<ActivitySlotInfo> slotSupplier;
4950
private final Scope metricsScope;
5051
private final PollActivityTaskQueueRequest pollRequest;
5152

@@ -57,11 +58,11 @@ public ActivityPollTask(
5758
@Nullable String buildId,
5859
boolean useBuildIdForVersioning,
5960
double activitiesPerSecond,
60-
Semaphore pollSemaphore,
61+
@Nonnull TrackingSlotSupplier<ActivitySlotInfo> slotSupplier,
6162
@Nonnull Scope metricsScope,
6263
@Nonnull Supplier<GetSystemInfoResponse.Capabilities> serverCapabilities) {
6364
this.service = Objects.requireNonNull(service);
64-
this.pollSemaphore = pollSemaphore;
65+
this.slotSupplier = slotSupplier;
6566
this.metricsScope = Objects.requireNonNull(metricsScope);
6667

6768
PollActivityTaskQueueRequest.Builder pollRequest =
@@ -92,13 +93,22 @@ public ActivityTask poll() {
9293
log.trace("poll request begin: " + pollRequest);
9394
}
9495
PollActivityTaskQueueResponse response;
96+
SlotPermit permit;
9597
boolean isSuccessful = false;
9698

9799
try {
98-
pollSemaphore.acquire();
100+
permit =
101+
slotSupplier.reserveSlot(
102+
new SlotReservationData(
103+
pollRequest.getTaskQueue().getName(),
104+
pollRequest.getIdentity(),
105+
pollRequest.getWorkerVersionCapabilities().getBuildId()));
99106
} catch (InterruptedException e) {
100107
Thread.currentThread().interrupt();
101108
return null;
109+
} catch (Exception e) {
110+
log.warn("Error while trying to reserve a slot for an activity", e.getCause());
111+
return null;
102112
}
103113

104114
try {
@@ -118,9 +128,20 @@ public ActivityTask poll() {
118128
ProtobufTimeUtils.toM3Duration(
119129
response.getStartedTime(), response.getCurrentAttemptScheduledTime()));
120130
isSuccessful = true;
121-
return new ActivityTask(response, pollSemaphore::release);
131+
slotSupplier.markSlotUsed(
132+
new ActivitySlotInfo(
133+
ActivityPollResponseToInfo.toActivityInfoImpl(
134+
response,
135+
pollRequest.getNamespace(),
136+
pollRequest.getTaskQueue().getNormalName(),
137+
false),
138+
pollRequest.getIdentity(),
139+
pollRequest.getWorkerVersionCapabilities().getBuildId()),
140+
permit);
141+
return new ActivityTask(
142+
response, () -> slotSupplier.releaseSlot(SlotReleaseReason.taskComplete(), permit));
122143
} finally {
123-
if (!isSuccessful) pollSemaphore.release();
144+
if (!isSuccessful) slotSupplier.releaseSlot(SlotReleaseReason.neverUsed(), permit);
124145
}
125146
}
126147
}

temporal-sdk/src/main/java/io/temporal/internal/worker/ActivityWorker.java

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@
3939
import io.temporal.serviceclient.rpcretry.DefaultStubServiceOperationRpcRetryOptions;
4040
import io.temporal.worker.MetricsType;
4141
import io.temporal.worker.WorkerMetricsTag;
42+
import io.temporal.worker.tuning.*;
4243
import java.util.Objects;
44+
import java.util.Optional;
4345
import java.util.concurrent.CompletableFuture;
44-
import java.util.concurrent.Semaphore;
4546
import java.util.concurrent.TimeUnit;
4647
import javax.annotation.Nonnull;
4748
import org.slf4j.Logger;
@@ -64,16 +65,16 @@ final class ActivityWorker implements SuspendableWorker {
6465
private final Scope workerMetricsScope;
6566
private final GrpcRetryer grpcRetryer;
6667
private final GrpcRetryer.GrpcRetryerOptions replyGrpcRetryerOptions;
67-
private final int executorSlots;
68-
private final Semaphore executorSlotsSemaphore;
68+
private final TrackingSlotSupplier<ActivitySlotInfo> slotSupplier;
6969

7070
public ActivityWorker(
7171
@Nonnull WorkflowServiceStubs service,
7272
@Nonnull String namespace,
7373
@Nonnull String taskQueue,
7474
double taskQueueActivitiesPerSecond,
7575
@Nonnull SingleWorkerOptions options,
76-
@Nonnull ActivityTaskHandler handler) {
76+
@Nonnull ActivityTaskHandler handler,
77+
@Nonnull TrackingSlotSupplier<ActivitySlotInfo> slotSupplier) {
7778
this.service = Objects.requireNonNull(service);
7879
this.namespace = Objects.requireNonNull(namespace);
7980
this.taskQueue = Objects.requireNonNull(taskQueue);
@@ -87,8 +88,8 @@ public ActivityWorker(
8788
this.replyGrpcRetryerOptions =
8889
new GrpcRetryer.GrpcRetryerOptions(
8990
DefaultStubServiceOperationRpcRetryOptions.INSTANCE, null);
90-
this.executorSlots = options.getTaskExecutorThreadPoolSize();
91-
this.executorSlotsSemaphore = new Semaphore(executorSlots);
91+
this.slotSupplier = slotSupplier;
92+
this.slotSupplier.setMetricsScope(this.workerMetricsScope);
9293
}
9394

9495
@Override
@@ -101,8 +102,7 @@ public boolean start() {
101102
options.getIdentity(),
102103
new TaskHandlerImpl(handler),
103104
pollerOptions,
104-
options.getTaskExecutorThreadPoolSize(),
105-
workerMetricsScope,
105+
slotSupplier.maximumSlots(),
106106
true);
107107
poller =
108108
new Poller<>(
@@ -115,7 +115,7 @@ public boolean start() {
115115
options.getBuildId(),
116116
options.isUsingBuildIdForVersioning(),
117117
taskQueueActivitiesPerSecond,
118-
executorSlotsSemaphore,
118+
this.slotSupplier,
119119
workerMetricsScope,
120120
service.getServerCapabilities()),
121121
this.pollTaskExecutor,
@@ -131,14 +131,14 @@ public boolean start() {
131131

132132
@Override
133133
public CompletableFuture<Void> shutdown(ShutdownManager shutdownManager, boolean interruptTasks) {
134-
String semaphoreName = this + "#executorSlotsSemaphore";
134+
String supplierName = this + "#executorSlots";
135135
return poller
136136
.shutdown(shutdownManager, interruptTasks)
137137
.thenCompose(
138138
ignore ->
139139
!interruptTasks
140-
? shutdownManager.waitForSemaphorePermitsReleaseUntimed(
141-
executorSlotsSemaphore, executorSlots, semaphoreName)
140+
? shutdownManager.waitForSupplierPermitsReleasedUnlimited(
141+
slotSupplier, supplierName)
142142
: CompletableFuture.completedFuture(null))
143143
.thenCompose(
144144
ignore ->
@@ -416,23 +416,33 @@ private void logExceptionDuringResultReporting(
416416

417417
private final class EagerActivityDispatcherImpl implements EagerActivityDispatcher {
418418
@Override
419-
public boolean tryReserveActivitySlot(
419+
public Optional<SlotPermit> tryReserveActivitySlot(
420420
ScheduleActivityTaskCommandAttributesOrBuilder commandAttributes) {
421-
return WorkerLifecycleState.ACTIVE.equals(ActivityWorker.this.getLifecycleState())
422-
&& Objects.equals(
423-
commandAttributes.getTaskQueue().getName(), ActivityWorker.this.taskQueue)
424-
&& ActivityWorker.this.executorSlotsSemaphore.tryAcquire();
421+
if (!WorkerLifecycleState.ACTIVE.equals(ActivityWorker.this.getLifecycleState())
422+
|| !Objects.equals(
423+
commandAttributes.getTaskQueue().getName(), ActivityWorker.this.taskQueue)) {
424+
return Optional.empty();
425+
}
426+
return ActivityWorker.this.slotSupplier.tryReserveSlot(
427+
new SlotReservationData(
428+
ActivityWorker.this.taskQueue, options.getIdentity(), options.getBuildId()));
425429
}
426430

427431
@Override
428-
public void releaseActivitySlotReservations(int slotCounts) {
429-
ActivityWorker.this.executorSlotsSemaphore.release(slotCounts);
432+
public void releaseActivitySlotReservations(Iterable<SlotPermit> permits) {
433+
for (SlotPermit permit : permits) {
434+
ActivityWorker.this.slotSupplier.releaseSlot(SlotReleaseReason.neverUsed(), permit);
435+
}
430436
}
431437

432438
@Override
433-
public void dispatchActivity(PollActivityTaskQueueResponse activity) {
439+
public void dispatchActivity(PollActivityTaskQueueResponse activity, SlotPermit permit) {
434440
ActivityWorker.this.pollTaskExecutor.process(
435-
new ActivityTask(activity, ActivityWorker.this.executorSlotsSemaphore::release));
441+
new ActivityTask(
442+
activity,
443+
() ->
444+
ActivityWorker.this.slotSupplier.releaseSlot(
445+
SlotReleaseReason.taskComplete(), permit)));
436446
}
437447
}
438448
}

temporal-sdk/src/main/java/io/temporal/internal/worker/EagerActivityDispatcher.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,33 @@
2222

2323
import io.temporal.api.command.v1.ScheduleActivityTaskCommandAttributesOrBuilder;
2424
import io.temporal.api.workflowservice.v1.PollActivityTaskQueueResponse;
25+
import io.temporal.worker.tuning.SlotPermit;
26+
import java.util.Optional;
2527

2628
public interface EagerActivityDispatcher {
27-
boolean tryReserveActivitySlot(ScheduleActivityTaskCommandAttributesOrBuilder commandAttributes);
29+
Optional<SlotPermit> tryReserveActivitySlot(
30+
ScheduleActivityTaskCommandAttributesOrBuilder commandAttributes);
2831

29-
void releaseActivitySlotReservations(int slotCounts);
32+
void releaseActivitySlotReservations(Iterable<SlotPermit> permits);
3033

31-
void dispatchActivity(PollActivityTaskQueueResponse activity);
34+
void dispatchActivity(PollActivityTaskQueueResponse activity, SlotPermit permit);
3235

3336
class NoopEagerActivityDispatcher implements EagerActivityDispatcher {
3437
@Override
35-
public boolean tryReserveActivitySlot(
38+
public Optional<SlotPermit> tryReserveActivitySlot(
3639
ScheduleActivityTaskCommandAttributesOrBuilder commandAttributes) {
37-
return false;
40+
return Optional.empty();
3841
}
3942

4043
@Override
41-
public void releaseActivitySlotReservations(int slotCounts) {
42-
if (slotCounts > 0)
44+
public void releaseActivitySlotReservations(Iterable<SlotPermit> permits) {
45+
if (permits.iterator().hasNext())
4346
throw new IllegalStateException(
4447
"Trying to release activity slots on a NoopEagerActivityDispatcher");
4548
}
4649

4750
@Override
48-
public void dispatchActivity(PollActivityTaskQueueResponse activity) {
51+
public void dispatchActivity(PollActivityTaskQueueResponse activity, SlotPermit permit) {
4952
throw new IllegalStateException(
5053
"Trying to dispatch activity on a NoopEagerActivityDispatcher");
5154
}

0 commit comments

Comments
 (0)