Skip to content

feat: virtual thread support - #3664

Merged
csviri merged 45 commits into
nextfrom
virtual-threads-and-shared-executors
Nov 5, 2026
Merged

csviri merged 45 commits into
nextfrom
virtual-threads-and-shared-executors

Conversation

@csviri

@csviri csviri commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

feat: virtual thread support

Adds a single flag that switches the framework's concurrent work over to virtual threads:

Operator operator = new Operator(override -> override.withUseVirtualThreads(true));

It can also be set with josdk.use-virtual-threads. It is off by default, so nothing changes for
existing operators.

Why

Reconcilers spend almost all their time blocked on the Kubernetes API server or on external
systems. Virtual threads make that kind of blocking cheap, so concurrency limits can be raised far
above what is reasonable with platform threads, without sizing and tuning thread pools.

What changes when the flag is on

Area Behavior
Reconciliations Run on virtual threads. withConcurrentReconciliationThreads(n) still caps how many run at once (enforced with a Semaphore).
Dependent workflows Same: virtual threads, still capped by withConcurrentWorkflowExecutorThreads(n).
Internal housekeeping The unbounded caching executor (starting informers, etc.) uses a virtual thread per task.
Scheduled tasks Polling event sources and retried or rescheduled reconciliations run on virtual threads. See the next section.
Kubernetes client The default client created by the framework runs its internal task executor (informer event dispatch, watch event delivery) on virtual threads. A client provided by the user is used as is.
JVM lifetime Virtual threads are always daemon threads, so they don't keep the JVM alive. If main returns after starting the operator and nothing else keeps the JVM running, call Operator.keepAlive() (already available, see the docs). The framework does not start such a thread on its own.

Java 25 only

Virtual threads are supported on Java 25 and later (JEP 491
removed the carrier pinning inside synchronized blocks, which the codebase uses extensively; on
Java 21 to 24 it can starve the carrier pool and deadlock startup). This is checked at runtime: on
an older JVM a warning is logged and platform threads are used, so one configuration works on every
Java version the operator runs on. The compilation target stays Java 17.

Scheduled executors (the larger part of this PR)

Previously PollingEventSource and TimerEventSource each created a java.util.Timer, and
PerResourcePollingEventSource created its own ScheduledThreadPoolExecutor. Each cost a thread
that the operator neither sized nor shut down. They now use scheduled executors that are created
and shut down in a defined way:

  • Polling event sources get an executor of their own. PollingEventSource and
    PerResourcePollingEventSource do not share an executor, so a slow poll of one source never
    delays another. Unless one is provided with withExecutorService on the configuration builder,
    the source creates a single-thread daemon executor when it starts and shuts it down when it stops
    (a new one on every start). With useVirtualThreads on, that executor runs the polls on virtual
    threads. A provided executor is used as is, is never shut down by the source, and may be shared
    deliberately.
  • ConfigurationService.getRetryAndRescheduleExecutorService() backs the retry and reschedule
    timer of every controller. It is managed by ExecutorServiceManager, sized with
    concurrentRetryAndRescheduleThreads() (default 2) or
    josdk.retry-and-reschedule.concurrent-threads, and can be replaced through
    ConfigurationServiceOverrider. The operator shuts it down on stop and creates a new one when
    started again.
  • The default pools use daemon threads, drop tasks scheduled for later on shutdown (so far-out
    reschedules don't delay termination), remove cancelled tasks eagerly, and let idle threads time
    out after 60 seconds.
  • Event sources resolve their executor on every start() rather than at construction. The polling
    and timer sources let a poll or handler already in flight finish when stopped, as the Timer
    they replace did. Note that the timer source does not schedule again the timers that were pending
    on an executor the operator replaced (which only happens when the operator is started again after
    a failed start), they are lost with the old executor.
  • ExecutorServiceManager.scheduledExecutorService() stays available for compatibility,
    deprecated, as a general purpose executor created on first use. The framework itself no longer
    uses it.

VirtualThreadScheduledExecutor

The JDK has no scheduled executor backed by virtual threads, so with the flag on the defaults are a
new package-private VirtualThreadScheduledExecutor: one daemon platform thread does the timing
and hands each fired task to a virtual-thread-per-task executor.

  • The number of scheduled tasks running at once is not limited in this mode, because they are
    typically blocking calls. withConcurrentRetryAndRescheduleThreads only applies to platform
    threads.
  • It keeps the semantics of the default platform executor: waiting and periodic tasks are discarded
    on shutdown(), and tasks that have already started are let finish. shutdownNow() interrupts
    running tasks.
  • Fixed-rate tasks make up missed executions after an overrun, as ScheduledThreadPoolExecutor
    does. Fixed-delay tasks re-arm from completion time.

Other changes

  • Executor helpers live in an internal ExecutorUtilsInternal.
  • ConfigLoader and ConfigurationServiceOverrider expose the new settings
    (josdk.use-virtual-threads, josdk.retry-and-reschedule.concurrent-threads).
  • The webpage and mysql-schema sample operators now run with virtual threads.
  • spotbugs exclusion for the VirtualThreadScheduledExecutor handles. They are ordered by remaining
    delay but must stay distinct by identity, as the JDK's own scheduled futures.

Documentation

  • operations/configuration.md: new "Virtual Threads" section, which points to "Keeping the JVM
    Alive" for operators whose main returns, and the new configuration keys.
  • eventing.md: "Threading of the polling event sources", with the per-source executor and who
    owns it.
  • migration/v5-7-migration.md: the changes to the executors of the polling and timer event
    sources, notably that an executor provided to a PerResourcePollingEventSource is not shut down
    by it anymore, plus the new deprecations.

Testing

Unit tests, with the virtual thread ones gated by @EnabledForJreRange(min = JAVA_25):

  • VirtualThreadScheduledExecutorTest: virtual thread execution, cancellation, fixed delay and
    fixed rate, overrun catch-up, shutdown semantics, unbounded concurrency.
  • VirtualThreadsTest: bounded virtual thread executor and fallback on old JVMs.
  • VirtualThreadsStartupDeadlockTest: startup with virtual threads does not deadlock.
  • ExecutorServiceManagerTest, ConfigurationServiceOverriderTest: lifecycle and overriding.
  • PerResourcePollingEventSourceTest, PollingEventSourceTest, TimerEventSourceTest: each source
    owning its executor (and not sharing it), provided executors left running, virtual threads,
    restart, stop and start ordering, polling resilience.
  • ExecutorUtilsInternalTest: idle pool threads time out and delayed tasks still run.

Integration test: VirtualThreadsIT runs a full operator with the flag on.

Known limitations and follow-ups

  • Only Java 25 and later is officially supported for virtual threads.

Copilot AI balanced review requested due to automatic review settings October 27, 2026 13:21
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 21d39387-3725-4387-b833-afd6b479d802

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved executor lifecycle, polling startup, and event-validation issues can lose work or prevent events from being recorded.

4 open issues
What changed in this PR

Adds opt-in virtual threads to Java Operator SDK while preserving concurrency limits and Java 17 fallback. The PR also centralizes scheduling and includes event-recording and desired-state fixes.

Changes:

  • Adds virtual-thread configuration, executor implementations, client integration, documentation, and tests.
  • Moves polling and retry scheduling to separate configurable executors with lifecycle management.
  • Updates event aggregation, long event names, and desired-state aspect handling.
File Description
sample-operators/​webpage/​src/​main/​java/​io/​javaoperatorsdk/​operator/​sample/​WebPageOperator.java Enables virtual threads in the sample.
operator-framework/​src/​test/​java/​io/​javaoperatorsdk/​operator/​dependent/​multiplemanagedexternaldependenttype/​MultipleManagedExternalDependentResourceReconciler.java Supplies context for shared polling.
operator-framework/​src/​test/​java/​io/​javaoperatorsdk/​operator/​config/​loader/​ConfigLoaderTest.java Tests new configuration keys.
operator-framework/​src/​test/​java/​io/​javaoperatorsdk/​operator/​baseapi/​virtualthreads/​VirtualThreadsTestReconciler.java Records thread type and concurrency.
operator-framework/​src/​test/​java/​io/​javaoperatorsdk/​operator/​baseapi/​virtualthreads/​VirtualThreadsIT.java Tests reconciliation threading and limits.
operator-framework/​src/​test/​java/​io/​javaoperatorsdk/​operator/​baseapi/​virtualthreads/​VirtualThreadsCustomResource.java Provides the integration-test resource.
operator-framework/​src/​main/​java/​io/​javaoperatorsdk/​operator/​config/​loader/​ConfigLoader.java Binds virtual-thread and scheduling settings.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​timer/​TimerEventSourceTest.java Tests executor ownership and restart.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​polling/​PollingEventSourceTest.java Tests shared scheduling and cancellation.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​polling/​PerResourcePollingEventSourceTest.java Tests polling restart behavior.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​processing/​dependent/​DesiredStateAspectAuditTest.java Adds desired-state aspect regression cases.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​api/​event/​EventRecorderAuditTest.java Adds event-recording regression cases.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​api/​event/​DefaultEventRecorderTest.java Tests collision-resistant name shortening.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​api/​config/​VirtualThreadsTest.java Tests executor and client threading.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​api/​config/​ExecutorServiceManagerTest.java Tests scheduler separation and lifecycle.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​api/​config/​ConfigurationServiceOverriderTest.java Tests threading configuration overrides.
operator-framework-core/​src/​test/​java/​io/​javaoperatorsdk/​operator/​api/​config/​BoundedVirtualThreadExecutorAuditTest.java Tests limits and pending-task shutdown.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​timer/​TimerEventSource.java Replaces timers with supplied or owned executors.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​polling/​PollingEventSource.java Adds shared scheduling and executor ownership.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​polling/​PollingConfigurationBuilder.java Adds custom polling executor configuration.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​polling/​PollingConfiguration.java Stores the optional polling executor.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​polling/​PerResourcePollingEventSource.java Resolves executors on startup and cancels tasks.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​source/​polling/​PerResourcePollingConfiguration.java Defers default executor selection.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​EventSources.java Accepts a retry scheduler supplier.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​event/​EventSourceManager.java Connects retries to the managed scheduler.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​dependent/​external/​PollingDependentResource.java Passes context to polling sources.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​processing/​dependent/​BulkDependentResourceReconciler.java Passes the user dependent to aspects.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​api/​reconciler/​DefaultContext.java Handles reentrant desired-state aspect lookups.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​api/​event/​DefaultEventSink.java Updates fields during named-event aggregation.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​api/​event/​DefaultEventRecorder.java Adds event timestamps and hashed name suffixes.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​api/​config/​VirtualThreads.java Implements virtual executors and concurrency bounds.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​api/​config/​Utils.java Adds named daemon scheduler utilities.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​api/​config/​ExecutorServiceManager.java Selects executors and manages scheduler lifecycle.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​api/​config/​ConfigurationServiceOverrider.java Exposes threading and scheduler overrides.
operator-framework-core/​src/​main/​java/​io/​javaoperatorsdk/​operator/​api/​config/​ConfigurationService.java Defines threading defaults and client integration.
docs/​content/​en/​docs/​documentation/​operations/​configuration.md Documents virtual threads and configuration keys.
docs/​content/​en/​docs/​documentation/​eventing.md Documents shared polling and executor ownership.

Review effort: Balanced

@csviri csviri linked an issue Oct 27, 2026 that may be closed by this pull request
@csviri
csviri requested a balanced review from Copilot October 28, 2026 10:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

csviri and others added 13 commits November 4, 2026 14:01
…s an override changes their settings

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ecutors when started again while running

- a per resource registration that races stop() is buffered until the next start instead of leaving a stale entry that blocks polling after a restart
- the timer and per resource polling event sources resolve their executor again when started while running, which happens when the operator is started again after a failed start and replaces its executors

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s, and let in-flight work finish when stopping owned executors

- PollingEventSource schedules its polling task again when started while running after the operator replaced its executors
- TimerEventSource schedules the timers pending on the replaced executor again for their remaining delay
- stopping a polling or timer event source that owns its executor no longer interrupts a poll or handler in flight

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
…se races in the polling event sources

- ExecutorServiceManager keeps the public scheduledExecutorService() of previous versions, deprecated, created on first use and replaced after a restart
- PerResourcePollingEventSource drops the result of a fetch from a previous run instead of overwriting the cache of the current one
- PollingEventSource ends start() cleanly when stopped during the initial fetch instead of failing on the shut down executor it owns

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ck in the fetching task

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…g the JVM alive

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…when the executor is replaced

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tor supplier

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…stom retry executor

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…utor and drop the executor supplier only constructor of TimerEventSource

- the timers pending on an executor the operator replaced are not scheduled again
- the constructor taking just an executor supplier is removed, the event source is mostly for internal usage

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…source polling starts instead of buffering registrations

- the registrations seen before start, or racing stop, are dropped, start registers every resource of the primary cache
- the registration racing the end of start is not handled anymore

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@csviri
csviri force-pushed the virtual-threads-and-shared-executors branch from c1a98c7 to 7939ed2 Compare November 4, 2026 22:01
csviri and others added 4 commits November 4, 2026 14:45
…ll found it gone from the cache

- a poll that finds its primary resource gone from the cache removes its registration, so a recreated resource is registered again instead of being skipped for the stale one
- onResourceDeleted is synchronized with the registration and scheduling of the polls

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… test name

- remove BoundedVirtualThreadExecutorAuditTest and the AUDIT_N override of VirtualThreadsStartupDeadlockTest
- describe in the Javadoc of VirtualThreadsStartupDeadlockTest what is guaranteed on which Java version
- rename the test checking that executeAndWaitForAllToComplete respects the bound of the executor

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ges in the migration guide

- the example of an executor with more threads uses PerResourcePollingConfigurationBuilder, a PollingEventSource runs a single periodic task
- the migration guide describes the executors of the polling event sources, the provided executor that is not shut down anymore and the deprecations

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Separately sampling remaining delays can violate the Comparable contract for handles with similar deadlines, potentially misordering futures in sorted collections or delay queues.

2 open issues

Review effort: Balanced

…ng their remaining delays

Sampling the remaining delay of each future separately could make a.compareTo(b) and b.compareTo(a) disagree for close deadlines, violating the Comparable contract.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human review is needed to assess concurrency and lifecycle risks involving untracked polling tasks, executor recreation after shutdown, and repeated cancellation that can unexpectedly interrupt running work.

1 open issue
1 resolved since last review

Review effort: Balanced

@csviri
csviri merged commit 07fb4e3 into next Nov 5, 2026
30 checks passed
@csviri
csviri deleted the virtual-threads-and-shared-executors branch November 5, 2026 00:07
csviri added a commit that referenced this pull request Nov 5, 2026
…#3665)

Correctness fixes of the event recorder, found while reviewing the virtual
thread support (#3664).

- Aggregating onto a named event now updates its type, reason and action
  along with the count, last timestamp and message, so a Warning repeated
  under the name of a Normal event is no longer reported as the Normal one.
  An occurrence without an action clears the earlier action, except on an
  event that carries an eventTime, where the reason stands in for it, as the
  API server requires an action for such events. The API server only makes
  these fields immutable for events.k8s.io/v1, not for the core API the sink
  writes to.
- Supplied event names longer than the Kubernetes limit are shortened with a
  hash of the full name instead of being plainly truncated, so distinct names
  sharing a long prefix no longer merge into one event. Trailing '-' and '.'
  are stripped before the hash is appended, for supplied and generated names
  alike, so a name is never cut into an invalid "...-.<hash>".
- Events about cluster scoped resources in the kube-system namespace carry an
  eventTime and an action (defaulting to the reason), which is what the API
  server requires to accept them there. Events in the default namespace are
  unchanged. The API server rejects such events in any other namespace, so the
  javadocs and docs no longer suggest the namespace the operator runs in.

Tests: regression and unit tests, a kube-api-test based
DefaultEventRecorderIntegrationTest recording events against a real API
server, and ITs for cluster scoped resources in default and kube-system.
csviri added a commit that referenced this pull request Nov 6, 2026
Add the useVirtualThreads flag (ConfigurationServiceOverrider.
withUseVirtualThreads, josdk.use-virtual-threads) that runs the
framework's concurrent work on virtual threads. It is off by default
and only supported on Java 25 or later (JEP 491); on an older JVM a
warning is logged and platform threads are used.

With the flag on:
- reconciliations and dependent workflows run on virtual threads, still
  capped by concurrentReconciliationThreads and
  concurrentWorkflowExecutorThreads
- the caching executor used for internal housekeeping uses a virtual
  thread per task
- the Kubernetes client created by the framework runs its internal task
  executor on virtual threads, a provided client is used as is
- scheduled tasks run on virtual threads through the new
  VirtualThreadScheduledExecutor, a daemon timing thread firing each
  task on a virtual thread, with the semantics of the platform default
  (waiting and periodic tasks discarded on shutdown, running tasks let
  finish, fixed rate tasks catch up after an overrun)

Scheduled work no longer relies on java.util.Timer:
- PollingEventSource and PerResourcePollingEventSource poll on an
  executor of their own, provided through the configuration builder or
  created, and shut down, by the event source, on virtual threads when
  the flag is on. A provided executor is never shut down by the source.
  They can be started again after a stop, and a poll in flight is not
  interrupted by it.
- PerResourcePollingEventSource registers the resources of the primary
  cache when it starts, drops the result of a poll of a previous run,
  and forgets the registration of a resource that is gone from the
  cache so that it is polled again if recreated
- TimerEventSource schedules on the retry and reschedule executor
  managed by ExecutorServiceManager, see
  ConfigurationService.getRetryAndRescheduleExecutorService and
  concurrentRetryAndRescheduleThreads. Timers pending on an executor the
  operator replaced are not scheduled again.
- the default pools use daemon threads, discard delayed tasks on
  shutdown and let idle threads time out

Behavior and API changes, described in the v5.7 migration guide:
- an executor provided to PerResourcePollingConfigurationBuilder is not
  shut down by the event source anymore, and the configuration returns
  a null executor when none was provided
- PollingEventSource(Class, PollingConfiguration), the
  TimerEventSource(String, boolean) constructor and
  ExecutorServiceManager.scheduledExecutorService() are deprecated
- ConfigurationServiceOverrider rebuilds the default retry executor when
  the flag or its thread count changes, a custom executor of the wrapped
  configuration is then replaced, see the Javadoc

Also: configuration keys in ConfigLoader, documentation (Virtual
Threads, polling event source threading, migration guide), the webpage
and mysql-schema samples run with virtual threads, VirtualThreadsIT,
and a spotbugs exclusion for the scheduled executor handles.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
csviri added a commit that referenced this pull request Nov 6, 2026
…#3665)

Correctness fixes of the event recorder, found while reviewing the virtual
thread support (#3664).

- Aggregating onto a named event now updates its type, reason and action
  along with the count, last timestamp and message, so a Warning repeated
  under the name of a Normal event is no longer reported as the Normal one.
  An occurrence without an action clears the earlier action, except on an
  event that carries an eventTime, where the reason stands in for it, as the
  API server requires an action for such events. The API server only makes
  these fields immutable for events.k8s.io/v1, not for the core API the sink
  writes to.
- Supplied event names longer than the Kubernetes limit are shortened with a
  hash of the full name instead of being plainly truncated, so distinct names
  sharing a long prefix no longer merge into one event. Trailing '-' and '.'
  are stripped before the hash is appended, for supplied and generated names
  alike, so a name is never cut into an invalid "...-.<hash>".
- Events about cluster scoped resources in the kube-system namespace carry an
  eventTime and an action (defaulting to the reason), which is what the API
  server requires to accept them there. Events in the default namespace are
  unchanged. The API server rejects such events in any other namespace, so the
  javadocs and docs no longer suggest the namespace the operator runs in.

Tests: regression and unit tests, a kube-api-test based
DefaultEventRecorderIntegrationTest recording events against a real API
server, and ITs for cluster scoped resources in default and kube-system.
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.

Support for Virtual Threads

3 participants