Skip to content

feat(schedules): implement schedule gRPC wiring and ScheduleClientImpl#1069

Merged
abhishekj720 merged 17 commits into
masterfrom
feat/schedule-impl
Jul 6, 2026
Merged

feat(schedules): implement schedule gRPC wiring and ScheduleClientImpl#1069
abhishekj720 merged 17 commits into
masterfrom
feat/schedule-impl

Conversation

@abhishekj720

@abhishekj720 abhishekj720 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What changed

Implements the gRPC wiring and client-side logic for the schedule API introduced in #1067.

gRPC stubs (IGrpcServiceStubs, GrpcServiceStubs): adds ScheduleAPIGrpc blocking stub, wired to the existing channel lifecycle.

Compatibility layer (Thrift↔proto):

  • EnumMapper: bidirectional scheduleOverlapPolicy, scheduleCatchUpPolicy
  • TypeMapper: bidirectional mapping for scheduleSpec, scheduleAction, schedulePolicies, schedulePauseInfo, scheduleState, backfillInfo, scheduleInfo, scheduleListEntry
  • RequestMapper: all 8 schedule request types (Thrift → proto), including overlap_policy and backfill_id on BackfillScheduleRequest
  • ResponseMapper: create/describe/list/update/delete/pause/unpause/backfill schedule responses (proto → Thrift)

WorkflowServiceGrpc: implements all 8 schedule methods by calling scheduleBlockingStub(), mapping through RequestMapper/ResponseMapper.

ScheduleClientImpl: new class implementing ScheduleClient. Wraps synchronous IWorkflowService calls in CompletableFuture on a dedicated daemon thread pool (keeps blocking gRPC I/O off ForkJoinPool.commonPool()). Maps Thrift gen types to clean client model types (Instant, Duration, client-side enums). Failures surface as CompletionException wrapping the original typed exception so callers can react to EntityNotExistsError etc. without double-unwrapping.

WorkflowClientInternal: scheduleClient() now returns ScheduleClientImpl.

Why

Previous state: all 8 schedule methods on IWorkflowService and ScheduleClient threw UnsupportedOperationException. This PR makes them functional end-to-end against a Cadence server with the schedule API enabled.

How it was tested

  • ./gradlew compileJava -x generateProto clean build
  • ./gradlew googleJavaFormat -x generateProto format clean
  • Unit tests in ScheduleTypesTest pass

Dependencies

Stacked on feat/schedule-service-contract (#1067).

Comment thread src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java Outdated
Comment thread src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java Outdated
Comment thread src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java Outdated
Comment thread src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java Outdated
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 470 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.13%. Comparing base (073c574) to head (561997d).

Files with missing lines Patch % Lines
...uber/cadence/internal/sync/ScheduleClientImpl.java 0.00% 152 Missing ⚠️
...ternal/compatibility/proto/mappers/TypeMapper.java 0.00% 116 Missing ⚠️
...nal/compatibility/proto/mappers/RequestMapper.java 0.00% 71 Missing ⚠️
...ber/cadence/serviceclient/WorkflowServiceGrpc.java 0.00% 51 Missing ⚠️
...al/compatibility/proto/mappers/ResponseMapper.java 0.00% 43 Missing ⚠️
...ternal/compatibility/proto/mappers/EnumMapper.java 0.00% 32 Missing ⚠️
...tibility/proto/serviceclient/GrpcServiceStubs.java 0.00% 4 Missing ⚠️
.../cadence/internal/sync/WorkflowClientInternal.java 0.00% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (0.00%) is below the target coverage (85.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your project check has failed because the head coverage (68.13%) is below the target coverage (85.00%). You can increase the head coverage or adjust the target coverage.

Files with missing lines Coverage Δ Complexity Δ
...ibility/proto/serviceclient/IGrpcServiceStubs.java 0.00% <ø> (ø) 0.00 <0.00> (ø)
...internal/sync/TestActivityEnvironmentInternal.java 95.37% <ø> (ø) 7.00 <0.00> (ø)
...internal/sync/TestWorkflowEnvironmentInternal.java 88.77% <ø> (ø) 18.00 <0.00> (ø)
...ence/internal/testservice/TestWorkflowService.java 73.87% <ø> (ø) 102.00 <0.00> (ø)
...er/cadence/serviceclient/IWorkflowServiceBase.java 89.52% <ø> (ø) 94.00 <0.00> (ø)
.../cadence/internal/sync/WorkflowClientInternal.java 70.67% <0.00%> (ø) 51.00 <0.00> (ø)
...tibility/proto/serviceclient/GrpcServiceStubs.java 0.00% <0.00%> (ø) 0.00 <0.00> (ø)
...ternal/compatibility/proto/mappers/EnumMapper.java 5.35% <0.00%> (-0.70%) 15.00 <0.00> (ø)
...al/compatibility/proto/mappers/ResponseMapper.java 0.00% <0.00%> (ø) 0.00 <0.00> (ø)
...ber/cadence/serviceclient/WorkflowServiceGrpc.java 0.00% <0.00%> (ø) 0.00 <0.00> (ø)
... and 3 more

... and 12 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 073c574...561997d. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@abhishekj720 abhishekj720 force-pushed the feat/schedule-impl branch 2 times, most recently from ce7ffd0 to b0984d0 Compare July 2, 2026 17:56
@gitar-bot

gitar-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 1 resolved / 4 findings

Implements schedule gRPC wiring and client logic, but requires addressing the dropped backfill overlap policy, improper use of the common ForkJoinPool for blocking calls, and the loss of typed error information in async wrappers.

⚠️ Bug: Backfill overlap policy is silently dropped in proto mapping

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164 📄 src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112

ScheduleBackfill documents its overlapPolicy as overriding the schedule's configured policy, and ScheduleClientImpl.backfillSchedule faithfully sets it on the Thrift BackfillScheduleRequest (request.setOverlapPolicy(...), ScheduleClientImpl.java:161-163). However, RequestMapper.backfillScheduleRequest (RequestMapper.java:1101-1112) only maps domain, scheduleId, startTime, and endTime — it never maps overlapPolicy onto the proto request. As confirmed, the generated proto com.uber.cadence.api.v1.BackfillScheduleRequest has no overlap_policy field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.

Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

💡 Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181

All async methods in ScheduleClientImpl use CompletableFuture.supplyAsync(...) / runAsync(...) without an explicit Executor, so the blocking synchronous gRPC calls run on the shared ForkJoinPool.commonPool(). Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

💡 Quality: Async failures wrapped in generic RuntimeException lose typed errors

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:70-74 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:99-103 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:164-168

Every async body in ScheduleClientImpl catches Exception and rethrows new RuntimeException(e). This obscures typed service exceptions (e.g. EntityNotExistsError, CadenceError) behind a bare RuntimeException wrapped inside CompletionException, forcing callers to unwrap two layers and inspect causes to react to specific failures. Consider preserving the original checked/service exception (e.g. via CompletionException or a documented client exception type) so callers can handle not-found / already-exists cases programmatically.

✅ 1 resolved
Bug: backfillSchedule NPEs on default-constructor ScheduleBackfill precision

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:159-160
ScheduleClientImpl.backfillSchedule computes nanos via bf.getStartTime().toEpochMilli() * 1_000_000L (and same for end time). This truncates any sub-millisecond precision of the supplied Instant, whereas the reverse conversion nanosToInstant preserves full nanosecond precision. If callers construct backfill ranges with nanosecond precision, the transmitted range will not match. Prefer converting the full Instant to nanos (e.g. instant.getEpochSecond() * 1_000_000_000L + instant.getNano()), consistent with how TypeMapper.timeToUnixNano handles other timestamps.

🤖 Prompt for agents
Code Review: Implements schedule gRPC wiring and client logic, but requires addressing the dropped backfill overlap policy, improper use of the common ForkJoinPool for blocking calls, and the loss of typed error information in async wrappers.

1. ⚠️ Bug: Backfill overlap policy is silently dropped in proto mapping
   Files: src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164, src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112

   `ScheduleBackfill` documents its `overlapPolicy` as overriding the schedule's configured policy, and `ScheduleClientImpl.backfillSchedule` faithfully sets it on the Thrift `BackfillScheduleRequest` (`request.setOverlapPolicy(...)`, ScheduleClientImpl.java:161-163). However, `RequestMapper.backfillScheduleRequest` (RequestMapper.java:1101-1112) only maps `domain`, `scheduleId`, `startTime`, and `endTime` — it never maps `overlapPolicy` onto the proto request. As confirmed, the generated proto `com.uber.cadence.api.v1.BackfillScheduleRequest` has no `overlap_policy` field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.
   
   Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

2. 💡 Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()
   Files: src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75, src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153, src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181

   All async methods in `ScheduleClientImpl` use `CompletableFuture.supplyAsync(...)` / `runAsync(...)` without an explicit `Executor`, so the blocking synchronous gRPC calls run on the shared `ForkJoinPool.commonPool()`. Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

3. 💡 Quality: Async failures wrapped in generic RuntimeException lose typed errors
   Files: src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:70-74, src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:99-103, src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:164-168

   Every async body in `ScheduleClientImpl` catches `Exception` and rethrows `new RuntimeException(e)`. This obscures typed service exceptions (e.g. `EntityNotExistsError`, `CadenceError`) behind a bare `RuntimeException` wrapped inside `CompletionException`, forcing callers to unwrap two layers and inspect causes to react to specific failures. Consider preserving the original checked/service exception (e.g. via `CompletionException` or a documented client exception type) so callers can handle not-found / already-exists cases programmatically.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@abhishekj720 abhishekj720 marked this pull request as draft July 2, 2026 18:02
Comment thread src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java Outdated
@gitar-bot

gitar-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 3 resolved / 5 findings

Implements gRPC wiring and ScheduleClientImpl to enable schedule service interactions, resolving previous issues with thread pool usage and backfill mapping. Ensure async error handling is updated to propagate typed exceptions instead of generic RuntimeExceptions and replace the unbounded cached thread pool.

💡 Quality: Async failures wrapped in generic RuntimeException lose typed errors

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:70-74 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:99-103 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:164-168

Every async body in ScheduleClientImpl catches Exception and rethrows new RuntimeException(e). This obscures typed service exceptions (e.g. EntityNotExistsError, CadenceError) behind a bare RuntimeException wrapped inside CompletionException, forcing callers to unwrap two layers and inspect causes to react to specific failures. Consider preserving the original checked/service exception (e.g. via CompletionException or a documented client exception type) so callers can handle not-found / already-exists cases programmatically.

💡 Performance: Unbounded cached thread pool for blocking schedule calls

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:64-70

The newly introduced BLOCKING_EXECUTOR uses Executors.newCachedThreadPool(...), which has no upper bound on thread count. Each concurrent schedule call that blocks on a synchronous gRPC round-trip holds a thread; under a burst of concurrent createSchedule/backfillSchedule/etc. calls, this can spawn an unbounded number of threads and exhaust memory/OS thread limits. Since the executor is a shared static field, this affects the whole process.

Consider a bounded pool (e.g. newFixedThreadPool or ThreadPoolExecutor with a max pool size and bounded queue) so backpressure is applied rather than allowing unbounded thread growth. This also keeps behavior predictable for a client library embedded in host applications.

✅ 3 resolved
Bug: backfillSchedule NPEs on default-constructor ScheduleBackfill precision

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:159-160
ScheduleClientImpl.backfillSchedule computes nanos via bf.getStartTime().toEpochMilli() * 1_000_000L (and same for end time). This truncates any sub-millisecond precision of the supplied Instant, whereas the reverse conversion nanosToInstant preserves full nanosecond precision. If callers construct backfill ranges with nanosecond precision, the transmitted range will not match. Prefer converting the full Instant to nanos (e.g. instant.getEpochSecond() * 1_000_000_000L + instant.getNano()), consistent with how TypeMapper.timeToUnixNano handles other timestamps.

Bug: Backfill overlap policy is silently dropped in proto mapping

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164 📄 src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112
ScheduleBackfill documents its overlapPolicy as overriding the schedule's configured policy, and ScheduleClientImpl.backfillSchedule faithfully sets it on the Thrift BackfillScheduleRequest (request.setOverlapPolicy(...), ScheduleClientImpl.java:161-163). However, RequestMapper.backfillScheduleRequest (RequestMapper.java:1101-1112) only maps domain, scheduleId, startTime, and endTime — it never maps overlapPolicy onto the proto request. As confirmed, the generated proto com.uber.cadence.api.v1.BackfillScheduleRequest has no overlap_policy field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.

Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181
All async methods in ScheduleClientImpl use CompletableFuture.supplyAsync(...) / runAsync(...) without an explicit Executor, so the blocking synchronous gRPC calls run on the shared ForkJoinPool.commonPool(). Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

🤖 Prompt for agents
Code Review: Implements gRPC wiring and ScheduleClientImpl to enable schedule service interactions, resolving previous issues with thread pool usage and backfill mapping. Ensure async error handling is updated to propagate typed exceptions instead of generic RuntimeExceptions and replace the unbounded cached thread pool.

1. 💡 Quality: Async failures wrapped in generic RuntimeException lose typed errors
   Files: src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:70-74, src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:99-103, src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:164-168

   Every async body in `ScheduleClientImpl` catches `Exception` and rethrows `new RuntimeException(e)`. This obscures typed service exceptions (e.g. `EntityNotExistsError`, `CadenceError`) behind a bare `RuntimeException` wrapped inside `CompletionException`, forcing callers to unwrap two layers and inspect causes to react to specific failures. Consider preserving the original checked/service exception (e.g. via `CompletionException` or a documented client exception type) so callers can handle not-found / already-exists cases programmatically.

2. 💡 Performance: Unbounded cached thread pool for blocking schedule calls
   Files: src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:64-70

   The newly introduced `BLOCKING_EXECUTOR` uses `Executors.newCachedThreadPool(...)`, which has no upper bound on thread count. Each concurrent schedule call that blocks on a synchronous gRPC round-trip holds a thread; under a burst of concurrent `createSchedule`/`backfillSchedule`/etc. calls, this can spawn an unbounded number of threads and exhaust memory/OS thread limits. Since the executor is a shared static field, this affects the whole process.
   
   Consider a bounded pool (e.g. `newFixedThreadPool` or `ThreadPoolExecutor` with a max pool size and bounded queue) so backpressure is applied rather than allowing unbounded thread growth. This also keeps behavior predictable for a client library embedded in host applications.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@abhishekj720 abhishekj720 marked this pull request as ready for review July 2, 2026 18:23
@abhishekj720 abhishekj720 changed the title feat: implement schedule gRPC wiring and ScheduleClientImpl feat(schedules): implement schedule gRPC wiring and ScheduleClientImpl Jul 2, 2026
@gitar-bot

gitar-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 5 resolved / 5 findings

Implements gRPC wiring and ScheduleClientImpl for schedule services, successfully addressing issues with default backfill NPEs, dropped overlap policies, and incorrect thread pool usage.

✅ 5 resolved
Bug: backfillSchedule NPEs on default-constructor ScheduleBackfill precision

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:159-160
ScheduleClientImpl.backfillSchedule computes nanos via bf.getStartTime().toEpochMilli() * 1_000_000L (and same for end time). This truncates any sub-millisecond precision of the supplied Instant, whereas the reverse conversion nanosToInstant preserves full nanosecond precision. If callers construct backfill ranges with nanosecond precision, the transmitted range will not match. Prefer converting the full Instant to nanos (e.g. instant.getEpochSecond() * 1_000_000_000L + instant.getNano()), consistent with how TypeMapper.timeToUnixNano handles other timestamps.

Bug: Backfill overlap policy is silently dropped in proto mapping

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164 📄 src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112
ScheduleBackfill documents its overlapPolicy as overriding the schedule's configured policy, and ScheduleClientImpl.backfillSchedule faithfully sets it on the Thrift BackfillScheduleRequest (request.setOverlapPolicy(...), ScheduleClientImpl.java:161-163). However, RequestMapper.backfillScheduleRequest (RequestMapper.java:1101-1112) only maps domain, scheduleId, startTime, and endTime — it never maps overlapPolicy onto the proto request. As confirmed, the generated proto com.uber.cadence.api.v1.BackfillScheduleRequest has no overlap_policy field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.

Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181
All async methods in ScheduleClientImpl use CompletableFuture.supplyAsync(...) / runAsync(...) without an explicit Executor, so the blocking synchronous gRPC calls run on the shared ForkJoinPool.commonPool(). Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

Quality: Async failures wrapped in generic RuntimeException lose typed errors

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:70-74 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:99-103 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:164-168
Every async body in ScheduleClientImpl catches Exception and rethrows new RuntimeException(e). This obscures typed service exceptions (e.g. EntityNotExistsError, CadenceError) behind a bare RuntimeException wrapped inside CompletionException, forcing callers to unwrap two layers and inspect causes to react to specific failures. Consider preserving the original checked/service exception (e.g. via CompletionException or a documented client exception type) so callers can handle not-found / already-exists cases programmatically.

Performance: Unbounded cached thread pool for blocking schedule calls

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:64-70
The newly introduced BLOCKING_EXECUTOR uses Executors.newCachedThreadPool(...), which has no upper bound on thread count. Each concurrent schedule call that blocks on a synchronous gRPC round-trip holds a thread; under a burst of concurrent createSchedule/backfillSchedule/etc. calls, this can spawn an unbounded number of threads and exhaust memory/OS thread limits. Since the executor is a shared static field, this affects the whole process.

Consider a bounded pool (e.g. newFixedThreadPool or ThreadPoolExecutor with a max pool size and bounded queue) so backpressure is applied rather than allowing unbounded thread growth. This also keeps behavior predictable for a client library embedded in host applications.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Adds com.uber.cadence.client.schedule — public Java API types for the
Schedules feature using Instant/Duration instead of raw nanoseconds.

Types added:
- ScheduleSpec          — cron expression, start/end time, jitter
- ScheduleAction        — wraps StartWorkflowAction for triggered workflows
- SchedulePolicies      — overlap, catch-up, and pause-on-failure behavior
- ScheduleState         — pause state (paused flag, reason, time, actor)
- ScheduleInfo          — runtime stats (runs, backfills, missed/skipped)
- ScheduleDescription   — full describe response; mutable for update calls
- ScheduleListEntry     — lightweight list entry from visibility store
- ListSchedulesResult   — paginated list response with next-page token
- ScheduleOverlapPolicy — SKIP_NEW, BUFFER, CONCURRENT, CANCEL_PREVIOUS, TERMINATE_PREVIOUS
- ScheduleCatchUpPolicy — SKIP, ONE, ALL
…tract types

- Change ScheduleBackfill start/end time from ZonedDateTime to Instant; the
  Thrift wire format stores epoch nanoseconds (timezone-unaware), so Instant
  is the correct representation and is consistent with ScheduleSpec
- Make ScheduleDescription fully immutable: remove all setters (setSpec,
  setAction, setPolicies, setMemo, setSearchAttributes), make fields final,
  and update class Javadoc to document the correct read-modify-write pattern
  via UpdateScheduleRequest
- Fix stale WorkflowClient Javadoc @link references across all client/schedule
  types (ScheduleDescription, ScheduleInfo, ScheduleListEntry, ScheduleState,
  ScheduleOverlapPolicy, SchedulePolicies, ListSchedulesResult); all schedule
  methods live on ScheduleClient, not WorkflowClient
- Soften getPauseReason() Javadoc: "Non-null only when isPaused() is true"
  changed to "null when not paused" to avoid implied contract beyond what the
  field stores
- Remove ScheduleTypesTest tests that exercised removed setters
  (scheduleDescription_setters, setSpec_rejectsNull, setAction_rejectsNull,
  setPolicies_rejectsNull); rewrite notEqualOnDifferentSpec and toString tests
  to construct ScheduleDescription directly without setters
…eduleDescription immutability

ScheduleBackfill is an immutable value type but was missing equals() and
hashCode(), unlike every other schedule model type. Add value-based
implementations over startTime, endTime, and overlapPolicy.

ScheduleDescription.getMemo() and getSearchAttributes() were returning
the caller-supplied map by reference, allowing post-construction mutation
and defeating the immutability established by making the fields final.
Defensive-copy and null-normalize both maps in the constructor
(null -> emptyMap, non-null -> unmodifiableMap of a new HashMap), matching
the pattern used by ScheduleInfo and ListSchedulesResult. Getters are now
guaranteed non-null.

Add tests covering the new equals/hashCode contract for ScheduleBackfill
and the null-normalization, unmodifiability, and non-aliasing guarantees
for ScheduleDescription maps.
describeSchedule now returns ScheduleDescription instead of
DescribeScheduleResponse, and listSchedules now returns
ListSchedulesResult instead of ListSchedulesResponse.

The client/schedule/ model layer was already documented and built to be
the public API surface; returning Thrift types was inconsistent with
that design. The implementation layer (PR4) will perform the
Thrift-to-model mapping.
…dulesResponse

Remove the custom ListSchedulesResult and ScheduleListEntry types; listSchedules
now returns the generated ListSchedulesResponse directly.
- Add ScheduleAPIGrpc blocking/future stubs to IGrpcServiceStubs and GrpcServiceStubs
- Add schedule enum mappers (OverlapPolicy, CatchUpPolicy) to EnumMapper
- Add schedule type mappers (ScheduleSpec, ScheduleAction, SchedulePolicies,
  ScheduleState, ScheduleInfo, ScheduleListEntry) to TypeMapper
- Add schedule request mappers (create/describe/update/delete/pause/unpause/
  backfill/list) to RequestMapper
- Add schedule response mappers (create/describe/list) to ResponseMapper
- Implement all 8 schedule methods in WorkflowServiceGrpc using ScheduleAPIGrpc
- Add ScheduleClientImpl implementing ScheduleClient, mapping Thrift responses
  to client model types (Instant, Duration, enums)
- Wire WorkflowClientInternal.scheduleClient() to return ScheduleClientImpl
…hedules

Remove the toListSchedulesResult mapping and unused ListSchedulesResult/
ScheduleListEntry imports; the service already returns ListSchedulesResponse.
…l overlap policy

Move all blocking gRPC calls off ForkJoinPool.commonPool() onto a dedicated
daemon thread pool. Warn when ScheduleBackfill.overlapPolicy is set, as the
current proto contract has no overlap_policy field on BackfillScheduleRequest
and the value is silently dropped before reaching the server.
…RuntimeException

Callers now unwrap one layer (CompletionException -> CadenceError) instead of
two (CompletionException -> RuntimeException -> CadenceError), making typed
error handling on EntityNotExistsError and similar exceptions straightforward.
…quest

The proto BackfillScheduleRequest already has overlap_policy (field 5) and
backfill_id (field 6); they were simply missing from the RequestMapper.
Wire both through and restore setting overlapPolicy in ScheduleClientImpl.
Comment thread src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java Outdated
Comment thread src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java Outdated
…ods to IWorkflowService

Default implementations wrap the blocking counterparts, consistent with
the existing ScheduleClientImpl executor pattern. Callers that need a
CompletableFuture handle can use these directly without reimplementing
the wrapping.
Comment thread src/main/java/com/uber/cadence/serviceclient/IWorkflowService.java Outdated
@abhishekj720 abhishekj720 marked this pull request as draft July 2, 2026 22:30
…esult references from ScheduleTypesTest

The ListSchedulesResult wrapper was removed in favour of ListSchedulesResponse
in a prior commit in this PR. The four listSchedulesResult_* tests that still
reference the removed type are deleted. A merge conflict during rebase also
caused the seven scheduleBackfill_* test methods to appear twice; the second
copy is removed.
@abhishekj720 abhishekj720 changed the base branch from feat/schedule-service-contract to master July 2, 2026 22:32
@gitar-bot

gitar-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 7 resolved / 8 findings

Implements end-to-end gRPC wiring for schedule APIs, resolving issues with thread pool usage, mapping logic, and test stability. Consider explicitly configuring the async schedule dispatcher to avoid potential contention on the common ForkJoinPool.

💡 Performance: Async schedule defaults dispatch blocking gRPC on commonPool

📄 src/main/java/com/uber/cadence/serviceclient/IWorkflowService.java:905-919

The eight new *ScheduleAsync default methods call CompletableFuture.supplyAsync(...) without an explicit Executor, so the blocking synchronous schedule calls (CreateSchedule, ListSchedules, etc.) run on ForkJoinPool.commonPool(). This is exactly the anti-pattern the team deliberately avoided in ScheduleClientImpl, which routes the same blocking calls through a dedicated daemon BLOCKING_EXECUTOR (ScheduleClientImpl.java:61-67, 82-90). If any caller uses these interface-level async variants, long-running/blocking gRPC I/O can starve the common pool and interfere with parallel streams and other framework code across the JVM.

Suggested fix: either provide an overload that accepts an Executor (and require callers to pass a dedicated pool), or remove these interface defaults and keep async dispatch confined to ScheduleClientImpl where a bounded/dedicated executor is already used. At minimum, document that these defaults run on the common pool and should not be used for blocking transports.

✅ 7 resolved
Bug: backfillSchedule NPEs on default-constructor ScheduleBackfill precision

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:159-160
ScheduleClientImpl.backfillSchedule computes nanos via bf.getStartTime().toEpochMilli() * 1_000_000L (and same for end time). This truncates any sub-millisecond precision of the supplied Instant, whereas the reverse conversion nanosToInstant preserves full nanosecond precision. If callers construct backfill ranges with nanosecond precision, the transmitted range will not match. Prefer converting the full Instant to nanos (e.g. instant.getEpochSecond() * 1_000_000_000L + instant.getNano()), consistent with how TypeMapper.timeToUnixNano handles other timestamps.

Bug: Backfill overlap policy is silently dropped in proto mapping

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164 📄 src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112
ScheduleBackfill documents its overlapPolicy as overriding the schedule's configured policy, and ScheduleClientImpl.backfillSchedule faithfully sets it on the Thrift BackfillScheduleRequest (request.setOverlapPolicy(...), ScheduleClientImpl.java:161-163). However, RequestMapper.backfillScheduleRequest (RequestMapper.java:1101-1112) only maps domain, scheduleId, startTime, and endTime — it never maps overlapPolicy onto the proto request. As confirmed, the generated proto com.uber.cadence.api.v1.BackfillScheduleRequest has no overlap_policy field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.

Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181
All async methods in ScheduleClientImpl use CompletableFuture.supplyAsync(...) / runAsync(...) without an explicit Executor, so the blocking synchronous gRPC calls run on the shared ForkJoinPool.commonPool(). Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

Bug: Duplicate test methods break compilation

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:599-613 📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:671-685
Seven test methods are defined twice with identical signatures in the same class: scheduleBackfill_twoArgGetters (599 & 671), scheduleBackfill_threeArgGetters (610 & 682), scheduleBackfill_equals (620 & 692), scheduleBackfill_equalsWithPolicy (631 & 703), scheduleBackfill_notEqualOnDifferentPolicy (641 & 713), scheduleBackfill_notEqualOnDifferentStart (650 & 722), and scheduleBackfill_toString (658 & 730). Java does not permit two methods with the same name and parameter list in one class, so this file will not compile (method is already defined). Delete the second copy (lines 670-740) of each duplicated method.

Bug: Tests reference nonexistent ListSchedulesResult type

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:533-547
Four tests (listSchedulesResult_tokenDefensiveCopy, listSchedulesResult_getTokenDoesNotExposeInternal, listSchedulesResult_nullTokenRoundTrips, listSchedulesResult_nullSchedulesNormalizesToEmpty) instantiate new ListSchedulesResult(...), but no ListSchedulesResult class exists in the codebase — a search finds it only in this test file. An earlier commit in this PR (refactor(schedule): replace ListSchedulesResult wrapper with ListSchedulesResponse) removed the wrapper in favor of ListSchedulesResponse. There is also no import for ListSchedulesResult, and no local class of that name. These references will fail to compile (cannot find symbol). Update the tests to use the current type (ListSchedulesResponse / relevant model) or remove them.

...and 2 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Implements end-to-end gRPC wiring for schedule APIs, resolving issues with thread pool usage, mapping logic, and test stability. Consider explicitly configuring the async schedule dispatcher to avoid potential contention on the common ForkJoinPool.

1. 💡 Performance: Async schedule defaults dispatch blocking gRPC on commonPool
   Files: src/main/java/com/uber/cadence/serviceclient/IWorkflowService.java:905-919

   The eight new `*ScheduleAsync` default methods call `CompletableFuture.supplyAsync(...)` without an explicit `Executor`, so the blocking synchronous schedule calls (`CreateSchedule`, `ListSchedules`, etc.) run on `ForkJoinPool.commonPool()`. This is exactly the anti-pattern the team deliberately avoided in `ScheduleClientImpl`, which routes the same blocking calls through a dedicated daemon `BLOCKING_EXECUTOR` (ScheduleClientImpl.java:61-67, 82-90). If any caller uses these interface-level async variants, long-running/blocking gRPC I/O can starve the common pool and interfere with parallel streams and other framework code across the JVM.
   
   Suggested fix: either provide an overload that accepts an `Executor` (and require callers to pass a dedicated pool), or remove these interface defaults and keep async dispatch confined to `ScheduleClientImpl` where a bounded/dedicated executor is already used. At minimum, document that these defaults run on the common pool and should not be used for blocking transports.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

These defaults dispatched blocking gRPC calls on ForkJoinPool.commonPool,
the same anti-pattern ScheduleClientImpl avoids with its dedicated
BLOCKING_EXECUTOR. Async dispatch belongs in ScheduleClientImpl, not at
the service interface layer.
@gitar-bot

gitar-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 8 resolved / 8 findings

Implements full gRPC wiring and client-side logic for the schedule API, resolving blocking I/O and mapping issues found during implementation. All pending issues addressed, enabling end-to-end functionality for schedule operations.

✅ 8 resolved
Bug: backfillSchedule NPEs on default-constructor ScheduleBackfill precision

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:159-160
ScheduleClientImpl.backfillSchedule computes nanos via bf.getStartTime().toEpochMilli() * 1_000_000L (and same for end time). This truncates any sub-millisecond precision of the supplied Instant, whereas the reverse conversion nanosToInstant preserves full nanosecond precision. If callers construct backfill ranges with nanosecond precision, the transmitted range will not match. Prefer converting the full Instant to nanos (e.g. instant.getEpochSecond() * 1_000_000_000L + instant.getNano()), consistent with how TypeMapper.timeToUnixNano handles other timestamps.

Bug: Backfill overlap policy is silently dropped in proto mapping

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164 📄 src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112
ScheduleBackfill documents its overlapPolicy as overriding the schedule's configured policy, and ScheduleClientImpl.backfillSchedule faithfully sets it on the Thrift BackfillScheduleRequest (request.setOverlapPolicy(...), ScheduleClientImpl.java:161-163). However, RequestMapper.backfillScheduleRequest (RequestMapper.java:1101-1112) only maps domain, scheduleId, startTime, and endTime — it never maps overlapPolicy onto the proto request. As confirmed, the generated proto com.uber.cadence.api.v1.BackfillScheduleRequest has no overlap_policy field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.

Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181
All async methods in ScheduleClientImpl use CompletableFuture.supplyAsync(...) / runAsync(...) without an explicit Executor, so the blocking synchronous gRPC calls run on the shared ForkJoinPool.commonPool(). Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

Bug: Duplicate test methods break compilation

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:599-613 📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:671-685
Seven test methods are defined twice with identical signatures in the same class: scheduleBackfill_twoArgGetters (599 & 671), scheduleBackfill_threeArgGetters (610 & 682), scheduleBackfill_equals (620 & 692), scheduleBackfill_equalsWithPolicy (631 & 703), scheduleBackfill_notEqualOnDifferentPolicy (641 & 713), scheduleBackfill_notEqualOnDifferentStart (650 & 722), and scheduleBackfill_toString (658 & 730). Java does not permit two methods with the same name and parameter list in one class, so this file will not compile (method is already defined). Delete the second copy (lines 670-740) of each duplicated method.

Bug: Tests reference nonexistent ListSchedulesResult type

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:533-547
Four tests (listSchedulesResult_tokenDefensiveCopy, listSchedulesResult_getTokenDoesNotExposeInternal, listSchedulesResult_nullTokenRoundTrips, listSchedulesResult_nullSchedulesNormalizesToEmpty) instantiate new ListSchedulesResult(...), but no ListSchedulesResult class exists in the codebase — a search finds it only in this test file. An earlier commit in this PR (refactor(schedule): replace ListSchedulesResult wrapper with ListSchedulesResponse) removed the wrapper in favor of ListSchedulesResponse. There is also no import for ListSchedulesResult, and no local class of that name. These references will fail to compile (cannot find symbol). Update the tests to use the current type (ListSchedulesResponse / relevant model) or remove them.

...and 3 more resolved from earlier reviews

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@abhishekj720 abhishekj720 marked this pull request as ready for review July 2, 2026 22:44
…re interface

Change all 8 schedule methods on IWorkflowService from blocking (throws
CadenceError) to CompletableFuture<T>. Implement via scheduleFutureStub()
in WorkflowServiceGrpc using a toCompletableFuture helper that bridges
ListenableFuture through Guava FutureCallback — no extra thread pool needed.

Drop BLOCKING_EXECUTOR from ScheduleClientImpl entirely; each method now
delegates directly to service.Foo(request). backfillSchedule fires all
requests concurrently via CompletableFuture.allOf(). Update stub
implementations in IWorkflowServiceBase, TestWorkflowService,
TestWorkflowEnvironmentInternal, and TestActivityEnvironmentInternal to
match the new return types.
Comment thread src/main/java/com/uber/cadence/serviceclient/WorkflowServiceGrpc.java Outdated
…eptionally

RequestMapper/EnumMapper calls are evaluated before toCompletableFuture is
entered, so any IllegalArgumentException (e.g. unrecognized enum values)
previously escaped synchronously instead of completing the returned future.
Wrap all 8 schedule methods in try/catch and use a failedFuture helper to
route mapping failures through the future contract.
@gitar-bot

gitar-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 10 resolved / 11 findings

Implements end-to-end gRPC wiring for the schedule API and introduces a dedicated executor for asynchronous operations, addressing multiple mapping and concurrency issues. Ensure response mapping is offloaded from network threads to prevent blocking gRPC execution.

💡 Performance: directExecutor runs response mapping on gRPC network threads

📄 src/main/java/com/uber/cadence/serviceclient/WorkflowServiceGrpc.java:36 📄 src/main/java/com/uber/cadence/serviceclient/WorkflowServiceGrpc.java:1450-1464 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:78

toCompletableFuture registers the FutureCallback with executor = MoreExecutors.directExecutor(). As a result, mapper.apply(result) (the ResponseMapper::... conversion) and, crucially, any dependent stages the caller attaches with thenApply/thenCompose without an explicit executor (e.g. ScheduleClientImpl.describeSchedule's toScheduleDescription, and end-user continuations) execute directly on the gRPC event-loop/callback thread. The PR's stated goal was to keep work off shared pools and avoid blocking gRPC I/O; running non-trivial mapping and user callbacks inline on gRPC threads can stall the transport. Consider completing the CompletableFuture on a dedicated executor rather than directExecutor().

✅ 10 resolved
Bug: backfillSchedule NPEs on default-constructor ScheduleBackfill precision

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:159-160
ScheduleClientImpl.backfillSchedule computes nanos via bf.getStartTime().toEpochMilli() * 1_000_000L (and same for end time). This truncates any sub-millisecond precision of the supplied Instant, whereas the reverse conversion nanosToInstant preserves full nanosecond precision. If callers construct backfill ranges with nanosecond precision, the transmitted range will not match. Prefer converting the full Instant to nanos (e.g. instant.getEpochSecond() * 1_000_000_000L + instant.getNano()), consistent with how TypeMapper.timeToUnixNano handles other timestamps.

Bug: Backfill overlap policy is silently dropped in proto mapping

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164 📄 src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112
ScheduleBackfill documents its overlapPolicy as overriding the schedule's configured policy, and ScheduleClientImpl.backfillSchedule faithfully sets it on the Thrift BackfillScheduleRequest (request.setOverlapPolicy(...), ScheduleClientImpl.java:161-163). However, RequestMapper.backfillScheduleRequest (RequestMapper.java:1101-1112) only maps domain, scheduleId, startTime, and endTime — it never maps overlapPolicy onto the proto request. As confirmed, the generated proto com.uber.cadence.api.v1.BackfillScheduleRequest has no overlap_policy field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.

Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181
All async methods in ScheduleClientImpl use CompletableFuture.supplyAsync(...) / runAsync(...) without an explicit Executor, so the blocking synchronous gRPC calls run on the shared ForkJoinPool.commonPool(). Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

Bug: Duplicate test methods break compilation

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:599-613 📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:671-685
Seven test methods are defined twice with identical signatures in the same class: scheduleBackfill_twoArgGetters (599 & 671), scheduleBackfill_threeArgGetters (610 & 682), scheduleBackfill_equals (620 & 692), scheduleBackfill_equalsWithPolicy (631 & 703), scheduleBackfill_notEqualOnDifferentPolicy (641 & 713), scheduleBackfill_notEqualOnDifferentStart (650 & 722), and scheduleBackfill_toString (658 & 730). Java does not permit two methods with the same name and parameter list in one class, so this file will not compile (method is already defined). Delete the second copy (lines 670-740) of each duplicated method.

Bug: Tests reference nonexistent ListSchedulesResult type

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:533-547
Four tests (listSchedulesResult_tokenDefensiveCopy, listSchedulesResult_getTokenDoesNotExposeInternal, listSchedulesResult_nullTokenRoundTrips, listSchedulesResult_nullSchedulesNormalizesToEmpty) instantiate new ListSchedulesResult(...), but no ListSchedulesResult class exists in the codebase — a search finds it only in this test file. An earlier commit in this PR (refactor(schedule): replace ListSchedulesResult wrapper with ListSchedulesResponse) removed the wrapper in favor of ListSchedulesResponse. There is also no import for ListSchedulesResult, and no local class of that name. These references will fail to compile (cannot find symbol). Update the tests to use the current type (ListSchedulesResponse / relevant model) or remove them.

...and 5 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Implements end-to-end gRPC wiring for the schedule API and introduces a dedicated executor for asynchronous operations, addressing multiple mapping and concurrency issues. Ensure response mapping is offloaded from network threads to prevent blocking gRPC execution.

1. 💡 Performance: directExecutor runs response mapping on gRPC network threads
   Files: src/main/java/com/uber/cadence/serviceclient/WorkflowServiceGrpc.java:36, src/main/java/com/uber/cadence/serviceclient/WorkflowServiceGrpc.java:1450-1464, src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:78

   `toCompletableFuture` registers the `FutureCallback` with `executor` = `MoreExecutors.directExecutor()`. As a result, `mapper.apply(result)` (the `ResponseMapper::...` conversion) and, crucially, any dependent stages the caller attaches with `thenApply`/`thenCompose` without an explicit executor (e.g. `ScheduleClientImpl.describeSchedule`'s `toScheduleDescription`, and end-user continuations) execute directly on the gRPC event-loop/callback thread. The PR's stated goal was to keep work off shared pools and avoid blocking gRPC I/O; running non-trivial mapping and user callbacks inline on gRPC threads can stall the transport. Consider completing the `CompletableFuture` on a dedicated executor rather than `directExecutor()`.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@gitar-bot

gitar-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 11 resolved / 11 findings

Implements end-to-end gRPC wiring for the schedule API and introduces a dedicated executor for asynchronous client operations, successfully resolving all mapping, thread pool, and test infrastructure findings.

✅ 11 resolved
Bug: backfillSchedule NPEs on default-constructor ScheduleBackfill precision

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:159-160
ScheduleClientImpl.backfillSchedule computes nanos via bf.getStartTime().toEpochMilli() * 1_000_000L (and same for end time). This truncates any sub-millisecond precision of the supplied Instant, whereas the reverse conversion nanosToInstant preserves full nanosecond precision. If callers construct backfill ranges with nanosecond precision, the transmitted range will not match. Prefer converting the full Instant to nanos (e.g. instant.getEpochSecond() * 1_000_000_000L + instant.getNano()), consistent with how TypeMapper.timeToUnixNano handles other timestamps.

Bug: Backfill overlap policy is silently dropped in proto mapping

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164 📄 src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112
ScheduleBackfill documents its overlapPolicy as overriding the schedule's configured policy, and ScheduleClientImpl.backfillSchedule faithfully sets it on the Thrift BackfillScheduleRequest (request.setOverlapPolicy(...), ScheduleClientImpl.java:161-163). However, RequestMapper.backfillScheduleRequest (RequestMapper.java:1101-1112) only maps domain, scheduleId, startTime, and endTime — it never maps overlapPolicy onto the proto request. As confirmed, the generated proto com.uber.cadence.api.v1.BackfillScheduleRequest has no overlap_policy field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.

Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181
All async methods in ScheduleClientImpl use CompletableFuture.supplyAsync(...) / runAsync(...) without an explicit Executor, so the blocking synchronous gRPC calls run on the shared ForkJoinPool.commonPool(). Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

Bug: Duplicate test methods break compilation

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:599-613 📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:671-685
Seven test methods are defined twice with identical signatures in the same class: scheduleBackfill_twoArgGetters (599 & 671), scheduleBackfill_threeArgGetters (610 & 682), scheduleBackfill_equals (620 & 692), scheduleBackfill_equalsWithPolicy (631 & 703), scheduleBackfill_notEqualOnDifferentPolicy (641 & 713), scheduleBackfill_notEqualOnDifferentStart (650 & 722), and scheduleBackfill_toString (658 & 730). Java does not permit two methods with the same name and parameter list in one class, so this file will not compile (method is already defined). Delete the second copy (lines 670-740) of each duplicated method.

Bug: Tests reference nonexistent ListSchedulesResult type

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:533-547
Four tests (listSchedulesResult_tokenDefensiveCopy, listSchedulesResult_getTokenDoesNotExposeInternal, listSchedulesResult_nullTokenRoundTrips, listSchedulesResult_nullSchedulesNormalizesToEmpty) instantiate new ListSchedulesResult(...), but no ListSchedulesResult class exists in the codebase — a search finds it only in this test file. An earlier commit in this PR (refactor(schedule): replace ListSchedulesResult wrapper with ListSchedulesResponse) removed the wrapper in favor of ListSchedulesResponse. There is also no import for ListSchedulesResult, and no local class of that name. These references will fail to compile (cannot find symbol). Update the tests to use the current type (ListSchedulesResponse / relevant model) or remove them.

...and 6 more resolved from earlier reviews

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

1 similar comment
@gitar-bot

gitar-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 11 resolved / 11 findings

Implements end-to-end gRPC wiring for the schedule API and introduces a dedicated executor for asynchronous client operations, successfully resolving all mapping, thread pool, and test infrastructure findings.

✅ 11 resolved
Bug: backfillSchedule NPEs on default-constructor ScheduleBackfill precision

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:159-160
ScheduleClientImpl.backfillSchedule computes nanos via bf.getStartTime().toEpochMilli() * 1_000_000L (and same for end time). This truncates any sub-millisecond precision of the supplied Instant, whereas the reverse conversion nanosToInstant preserves full nanosecond precision. If callers construct backfill ranges with nanosecond precision, the transmitted range will not match. Prefer converting the full Instant to nanos (e.g. instant.getEpochSecond() * 1_000_000_000L + instant.getNano()), consistent with how TypeMapper.timeToUnixNano handles other timestamps.

Bug: Backfill overlap policy is silently dropped in proto mapping

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:150-164 📄 src/main/java/com/uber/cadence/internal/compatibility/proto/mappers/RequestMapper.java:1101-1112
ScheduleBackfill documents its overlapPolicy as overriding the schedule's configured policy, and ScheduleClientImpl.backfillSchedule faithfully sets it on the Thrift BackfillScheduleRequest (request.setOverlapPolicy(...), ScheduleClientImpl.java:161-163). However, RequestMapper.backfillScheduleRequest (RequestMapper.java:1101-1112) only maps domain, scheduleId, startTime, and endTime — it never maps overlapPolicy onto the proto request. As confirmed, the generated proto com.uber.cadence.api.v1.BackfillScheduleRequest has no overlap_policy field, so the user-specified override is silently discarded and never reaches the server. This makes the documented per-backfill overlap override a no-op.

Suggested fix: either (a) surface the limitation by throwing/logging when a non-null overlap policy is supplied but cannot be transmitted, or (b) update the underlying proto/service contract to carry the overlap policy and map it through. At minimum, document that per-backfill overlap policy is currently unsupported so callers are not misled.

Performance: Blocking gRPC calls dispatched on ForkJoinPool.commonPool()

📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:68-75 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:152-153 📄 src/main/java/com/uber/cadence/internal/sync/ScheduleClientImpl.java:180-181
All async methods in ScheduleClientImpl use CompletableFuture.supplyAsync(...) / runAsync(...) without an explicit Executor, so the blocking synchronous gRPC calls run on the shared ForkJoinPool.commonPool(). Long-running/blocking I/O on the common pool can starve other parallel-stream and CompletableFuture work in the JVM, and the pool is sized for CPU-bound tasks. Consider passing a dedicated executor (bounded thread pool) for these blocking service calls.

Bug: Duplicate test methods break compilation

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:599-613 📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:671-685
Seven test methods are defined twice with identical signatures in the same class: scheduleBackfill_twoArgGetters (599 & 671), scheduleBackfill_threeArgGetters (610 & 682), scheduleBackfill_equals (620 & 692), scheduleBackfill_equalsWithPolicy (631 & 703), scheduleBackfill_notEqualOnDifferentPolicy (641 & 713), scheduleBackfill_notEqualOnDifferentStart (650 & 722), and scheduleBackfill_toString (658 & 730). Java does not permit two methods with the same name and parameter list in one class, so this file will not compile (method is already defined). Delete the second copy (lines 670-740) of each duplicated method.

Bug: Tests reference nonexistent ListSchedulesResult type

📄 src/test/java/com/uber/cadence/client/schedule/ScheduleTypesTest.java:533-547
Four tests (listSchedulesResult_tokenDefensiveCopy, listSchedulesResult_getTokenDoesNotExposeInternal, listSchedulesResult_nullTokenRoundTrips, listSchedulesResult_nullSchedulesNormalizesToEmpty) instantiate new ListSchedulesResult(...), but no ListSchedulesResult class exists in the codebase — a search finds it only in this test file. An earlier commit in this PR (refactor(schedule): replace ListSchedulesResult wrapper with ListSchedulesResponse) removed the wrapper in favor of ListSchedulesResponse. There is also no import for ListSchedulesResult, and no local class of that name. These references will fail to compile (cannot find symbol). Update the tests to use the current type (ListSchedulesResponse / relevant model) or remove them.

...and 6 more resolved from earlier reviews

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@abhishekj720 abhishekj720 merged commit 9f53be6 into master Jul 6, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants