Skip to content

libeventlog: wait for pending commits to complete#7271

Merged
mergify[bot] merged 5 commits intoflux-framework:masterfrom
chu11:issue7257_eventlogger_wait
Jan 20, 2026
Merged

libeventlog: wait for pending commits to complete#7271
mergify[bot] merged 5 commits intoflux-framework:masterfrom
chu11:issue7257_eventlogger_wait

Conversation

@chu11
Copy link
Member

@chu11 chu11 commented Jan 10, 2026

Problem: Both eventlogger_commit() and eventlogger_flush() assume all prior pending commits have completed. This is not a problem under normal conditions, as there is always some "current" event data in the eventlogger that needs to be written out, and commit/flush can wait for that commit to be completed.

However, under some circumstances there may not be any "current" data to be written out. This can lead to a race as code returns a fulfilled future, assuming all prior pending commits have completed.

Its worth noting there is no current use of eventlogger_commit() that could lead to a race. Races with eventlogger_flush() are
theoretically possible in the shell.

Solution: When there are no "current" events to be written out and there are pending commits waiting to be completed, wait for the most recent pending commit to complete before fulfilling the future. We accomplish this by creating a new "empty" batch of events and commit that empty set of events. The future created by this "empty" batch can be waited on.

Fixes #7257

@chu11 chu11 force-pushed the issue7257_eventlogger_wait branch 2 times, most recently from 538c437 to 6758039 Compare January 10, 2026 00:29
Comment on lines +200 to +216
/* If pending list is not empty, we need to wait until all
* pending batches have completed before returning.
* Accomplish this by creating a new batch and committing this
* "empty batch". While this may be inefficient (sending an
* RPC that does nothing), this ensures that everything else
* in this API works just as though a non-empty-batch was
* committed.
*
* N.B. eventlog_batch_get() places this new batch on the
* pending list.
*/

if (!(batch = eventlog_batch_get (ev)))
return NULL;
if (batchp)
*batchp = batch;
ev->current = NULL;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good catch!

I'm not sure how often an empty batch is actually committed. If it is a significant amount, perhaps we should explore other solutions that don't send an empty RPC.

For example, append empty batches to the pending list without sending an RPC. When non-empty batches complete, check if the next batch in the list and "complete" that batch as well. This would require saving the future to the eventlog batch object.

If empty batches are very rare, then the current solution here is simpler and is probably fine.

Copy link
Member Author

@chu11 chu11 Jan 20, 2026

Choose a reason for hiding this comment

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

I'm not sure how often an empty batch is actually committed. If it is a significant amount, perhaps we should explore other solutions that don't send an empty RPC.

It should be quite rare. If you take a look at #6581, the max "ops" that the kvs saw (within some sampling window) was 11K.

Looking at corona right now, the max is 21 (not a typo, not 21K) :-) Tuolomne it's 29K.

Perhaps a minor bump up to .... 8K or 16K may be ok?

(Edit: I just realized, my comment covered a bit of a this one and follow on #7291)

For example, append empty batches to the pending list without sending an RPC. When non-empty batches complete, check if the next batch in the list and "complete" that batch as well. This would require saving the future to the eventlog batch object.

If empty batches are very rare, then the current solution here is simpler and is probably fine.

I originally implemented this much like you describe. Just creating a dummy future with flux_future_create() and manually fulfilling it as needed.

The fallout from this was eventlog_flush() and its call to flux_future_wait_for(). Since there was no init function to the future, the internal "now" rpc was destined to hang forever. Which is why it lead me down the path of sending an empty batch RPC (thus my comment of preserving all other aspects of the API).

Copy link
Contributor

@grondo grondo left a comment

Choose a reason for hiding this comment

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

LGTM! Minor comments inline.

*/
if ((f = flux_future_create (NULL, NULL))) {
flux_future_set_reactor (f, flux_get_reactor (ev->h));
flux_future_set_flux (f, ev->h);
Copy link
Contributor

Choose a reason for hiding this comment

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

Commit message typo: "Set the flux handle via flux_future_get_flux()" should be "flux_future_set_flux()"

void *arg;
};

static struct eventlog_batch * eventlog_batch_get (struct eventlogger *ev);
Copy link
Contributor

Choose a reason for hiding this comment

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

Commit message title: libevent: should be libeventlog:

if (batchp)
*batchp = batch;
ev->current = NULL;
/* fallthrough */
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: expand this comment to describe what happens to the empty batch on fallthrough, e.g.:

/* fallthrough: empty batch is now pending and will serialize behind previous batches */

chu11 added 5 commits January 20, 2026 10:54
Problem: Modern flux-core coding style is to place each function
parameter on a different line if the line length exceeds 80 chars.
This is violated in libeventlog.

Fix the unmodern coding style.
Problem: In the libeventlog eventlogger, an "empty batch" is immediately
fulfilled with a new future that has a reactor set with
flux_future_set_reactor().  This can lead to downstream problems because
often callers get the reactor via the flux handle and flux_future_get_flux().

Set the flux handle via flux_future_set_flux() instead.
Problem: The naming of some of the internal functions gets a little
confusing.

Solution: Rename some functions as follows:

commit_batch -> commit
- this is the internal wrapper around eventlogger_commit()

eventlogger_commit_batch -> commit_batch
- this does the actual commit of a batch, and it is not a public function
  so remove the "eventlogger_" prefix.

commit_cb -> commit_batch_cb
- this is the callback from the new commit_batch()
Problem: The commit_batch() function in the eventlogger handles
a special case when the batch is NULL.  The commit_batch() function
should only deal with the specific case of committing a batch
to the KVS.

Move the special case handling from commit_batch() to commit().
Problem: Both eventlogger_commit() and eventlogger_flush() assume
all prior pending commits have completed.  This is not a problem
under normal conditions, as there is always some "current" event
data in the eventlogger that needs to be written out, and
commit/flush can wait for that commit to be completed.

However, under some circumstances there may not be any "current"
data to be written out.  This can lead to a race as code returns
a fulfilled future, assuming all prior pending commits have
completed.

Its worth nothing there is no current use of eventlogger_commit()
that could lead to a race.  Races with eventlogger_flush() are
theoretically possible in the shell.

Solution: When there are no "current" events to be written out and
there are pending commits waiting to be completed, wait for the
most recent pending commit to complete before fulfilling the future.
We accomplish this by creating a new "empty" batch of events and
commit that empty set of events.  The future created by this "empty"
batch can be waited on.

Fixes flux-framework#7257
@chu11 chu11 force-pushed the issue7257_eventlogger_wait branch from 6758039 to 91738ce Compare January 20, 2026 19:26
@chu11
Copy link
Member Author

chu11 commented Jan 20, 2026

thanks! will set MWP.

@grondo
Copy link
Contributor

grondo commented Jan 20, 2026

Oh, I noticed the PR title has libevent: as well. maybe I'll just fix that so it doesn't get into the release notes.

@grondo grondo changed the title libevent: wait for pending commits to complete libeventlog: wait for pending commits to complete Jan 20, 2026
@mergify mergify bot added the queued label Jan 20, 2026
@mergify mergify bot merged commit 8f17bb1 into flux-framework:master Jan 20, 2026
58 of 60 checks passed
@mergify
Copy link
Contributor

mergify bot commented Jan 20, 2026

Merge Queue Status

✅ The pull request has been merged at 91738ce

This pull request spent 6 seconds in the queue, with no time running CI.
The checks were run in-place.

Required conditions to merge
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #changes-requested-reviews-by = 0 [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = validate commits
    • check-neutral = validate commits
    • check-skipped = validate commits
  • any of [🛡 GitHub branch protection]:
    • check-success = address-sanitizer check
    • check-neutral = address-sanitizer check
    • check-skipped = address-sanitizer check
  • any of [🛡 GitHub branch protection]:
    • check-success = coverage
    • check-neutral = coverage
    • check-skipped = coverage
  • any of [🛡 GitHub branch protection]:
    • check-success = focal - py3.8
    • check-neutral = focal - py3.8
    • check-skipped = focal - py3.8
  • any of [🛡 GitHub branch protection]:
    • check-success = docs/readthedocs.org:flux-core
    • check-neutral = docs/readthedocs.org:flux-core
    • check-skipped = docs/readthedocs.org:flux-core
  • any of [🛡 GitHub branch protection]:
    • check-success = inception
    • check-neutral = inception
    • check-skipped = inception
  • any of [🛡 GitHub branch protection]:
    • check-success = flux-sched check
    • check-neutral = flux-sched check
    • check-skipped = flux-sched check
  • any of [🛡 GitHub branch protection]:
    • check-success = el8 - system,coverage
    • check-neutral = el8 - system,coverage
    • check-skipped = el8 - system,coverage
  • any of [🛡 GitHub branch protection]:
    • check-success = spelling
    • check-neutral = spelling
    • check-skipped = spelling
  • any of [🛡 GitHub branch protection]:
    • check-success = el8 - ascii
    • check-neutral = el8 - ascii
    • check-skipped = el8 - ascii
  • any of [🛡 GitHub branch protection]:
    • check-success = bookworm - 32 bit
    • check-neutral = bookworm - 32 bit
    • check-skipped = bookworm - 32 bit
  • any of [🛡 GitHub branch protection]:
    • check-success = flux-accounting check
    • check-neutral = flux-accounting check
    • check-skipped = flux-accounting check
  • any of [🛡 GitHub branch protection]:
    • check-success = python linting
    • check-neutral = python linting
    • check-skipped = python linting
  • any of [🛡 GitHub branch protection]:
    • check-success = el9 - test-install
    • check-neutral = el9 - test-install
    • check-skipped = el9 - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = fedora40 - clang-18
    • check-neutral = fedora40 - clang-18
    • check-skipped = fedora40 - clang-18
  • any of [🛡 GitHub branch protection]:
    • check-success = noble - test-install
    • check-neutral = noble - test-install
    • check-skipped = noble - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = alpine - test-install
    • check-neutral = alpine - test-install
    • check-skipped = alpine - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = fedora40 - test-install
    • check-neutral = fedora40 - test-install
    • check-skipped = fedora40 - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = bookworm - test-install
    • check-neutral = bookworm - test-install
    • check-skipped = bookworm - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = jammy - test-install
    • check-neutral = jammy - test-install
    • check-skipped = jammy - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = flux-pam check
    • check-neutral = flux-pam check
    • check-skipped = flux-pam check
  • any of [🛡 GitHub branch protection]:
    • check-success = flux-pmix check
    • check-neutral = flux-pmix check
    • check-skipped = flux-pmix check
  • any of [🛡 GitHub branch protection]:
    • check-success = bookworm - gcc-12,distcheck
    • check-neutral = bookworm - gcc-12,distcheck
    • check-skipped = bookworm - gcc-12,distcheck

@mergify mergify bot removed the queued label Jan 20, 2026
@codecov
Copy link

codecov bot commented Jan 20, 2026

Codecov Report

❌ Patch coverage is 77.77778% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.65%. Comparing base (428742c) to head (91738ce).
⚠️ Report is 7 commits behind head on master.

Files with missing lines Patch % Lines
src/common/libeventlog/eventlogger.c 77.77% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #7271      +/-   ##
==========================================
- Coverage   83.66%   83.65%   -0.01%     
==========================================
  Files         559      559              
  Lines       93260    93267       +7     
==========================================
- Hits        78025    78022       -3     
- Misses      15235    15245      +10     
Files with missing lines Coverage Δ
src/common/libeventlog/eventlogger.c 89.58% <77.77%> (-1.77%) ⬇️

... and 8 files with indirect coverage changes

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

@chu11 chu11 deleted the issue7257_eventlogger_wait branch January 20, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

eventlogger: flush/commit should wait for pending requests to complete

2 participants