Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

ExitChecker: add lnx/bsd single-pid exit watchers#4600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
kitbellew wants to merge1 commit intoscala-native:main
base:main
Choose a base branch
Loading
fromkitbellew:4600

Conversation

@kitbellew
Copy link
Contributor

@kitbellewkitbellew commentedOct 30, 2025
edited
Loading

Expandpidfd field in UnixProcessGen2 to take an event watcher factory object which initializes an event watcher which can be used to monitor this process for completion.

@kitbellewkitbellew changed the titleUnixProcessGen2: save kqueue in ctorEventWatcher: use linux/bsd process exit monitorsOct 31, 2025
@kitbellewkitbellewforce-pushed the4600 branch 5 times, most recently froma9fa248 to5b13bddCompareNovember 1, 2025 02:36
@LeeTibbert
Copy link
Contributor

@kitbellew

Could you let me know when this this draft PR stabilizes and gets re-based? Thanks.

No rush, I have at least two reported SN defects which will a significant amount of time.

Sorting outos-lib andkyo issues has the benefit of exerciser recent SN 0.5.10-SNAPSHOT changes.

I gave this PR an initial read or three. I'd like to be able to study a 'near final' version and not have
that version go from Draft to committed before discussion.

Knowing that such a pause will exist will allow me to focus on exploring some reports that would show that
work accomplished to date is solid or to flush out remaining defects.

@kitbellewkitbellewforce-pushed the4600 branch 19 times, most recently from2579787 toe31af30CompareNovember 3, 2025 16:01
@kitbellewkitbellewforce-pushed the4600 branch 3 times, most recently from12b396f tod604f60CompareNovember 3, 2025 19:42
@kitbellewkitbellew changed the titleEventWatcher: use linux/bsd process exit monitorsEventWatcher: add lnx/bsd single-pid exit watchersNov 3, 2025
@kitbellewkitbellew marked this pull request as ready for reviewNovember 3, 2025 19:44
@kitbellew
Copy link
ContributorAuthor

@LeeTibbert as requested, this one is ready to review. cc@WojciechMazur

Please review just the last commit, as the PR contains#4622 and#4623 and is to be merged after them.

@LeeTibbert
Copy link
Contributor

@kitbellew

Thank you for the heads up and pause.

I am beginning to review this PR for concept. I'll focus on theWatcher changes.
I am running/have_run out of time for this session. Will pick up when I am fresh,
probably tomorrow morning UTC-4.

Delay may work in my/our favor. The required prerequsite PR may have been
merged by then, clearing away some shrubbery and making it easier to focus
on the intended changes.

I suspect that this is a major architectural change and deserves close review
and probably theoretical analysis.

No disrespect.


val factoryOpt: Option[Factory] =
if (LinktimeInfo.isWindows) None
else if (LinktimeInfo.is32BitPlatform) None // TODO: wonder why
Copy link
Contributor

@LeeTibbertLeeTibbertNov 3, 2025
edited
Loading

Choose a reason for hiding this comment

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

good question in TODO

I have not traced far enough to determine what 'None'
does. I'll keep my eyes open for what the caller does.

I think a return value of None does not mean the obvious
"No watcher needed' but rather "use traditional, since replaced, C++ watcher".

I believe that was the intent of the, say 0.5.9 code. I hope
the code actually implemented that intent.

I never took the time to determine if 32 bit Linux had pidfd.
I do know that I did not have access to a machine where
I could test it. No X32 cases in CI in those days.
So, Ichickened out followed a conservative path
and kept it using the traditional watcher.

Only you know if making changes for 32 bit systems is worth your time. My suggestion is that it is not economic,
at least until there is some known defect specific to those machines.

I checked the multiarch .yml and the 64 & 32 bit tests
use Java 17. To check the bookkeeping, that means
thatProcessTestOnJDK9 should be running.
When I tried the experiment of doing away with a Watcher
altogether, those were the tests which showed that
some kind of Watcher is needed.

The fact that X86 tests are passing
ProcessTestOnJDK9 leads, weakly, to the conclusion
thatsome kind of Watcher must be running there.

privatevalhasProcessesToWatch= lock.newCondition()

defwatchForTermination(handle:GenericProcessHandle):Unit= {
processes.put(handle.pid(), handle)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not touched by this PR but worth mentioning
in case it is touched in the future.

Putting the process.put() after the isAlive() would
save effort in the hopefully rare case that the watcherThread is not alive,

if (pidfd < 0) None else Some(pidfd)
}

/* We use ppoll here as an epoll descriptor may not be persisted; if multiple
Copy link
Contributor

@LeeTibbertLeeTibbertNov 4, 2025
edited
Loading

Choose a reason for hiding this comment

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

When dealing with one pidfd, ppoll() is much lighter weight and almost certainly takes fewer CPU cycles. I say
'almost certainly' because I do not have numbers and
I have not traced to execution path to see if ppoll is implemented in terms of epoll.

The ppoll() call does not change the signal mask. Time
to date has shown that to not be necessary.

I believe the description described in the rest of the call
may well be misdirection.

Earlier this week a contributor explained to me
the path from the pidfd thru to the Linux internal
reference. All my reading about epoll() indicates
that multiple calls to epoll on the same pidfd will
be notified when the pidfd state changes. That is
all epoll waiting on the same internal slot.

Now, I have not run a program to test this out and
I have not traced Linux internals.

You probably would not have entered the comment
if you did not have a good reason.

Describing the lack of epoll() answers the question that
a reader might have before they formulate it. They may
be sufficient.

The epoll description may be somewhere between too-much-information (the devo of 10,000 comments) says
or information pending validation.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know how epoll behaves internally, can go by two points:

  • one is what ChatGPT keeps telling me; I find that if the LLM persists despite constant challenges and disbelief, it tends to be right
    • for instance, it kept insisting that some code in MacOS's implementation ofkevent makesNOTE_EXITSTATUS completely unreliable, and I had to try it myself before running into problems and agreeing
    • in this case, it is firm in that both epoll_wait and kevent are not guaranteed to be woken up by the kernel if there are other waiters for the same event
  • two is this: this is not the last commit in my sequence, and there are a few more leading to an exit watcher which persists kqueue (bsd) and epoll (linux) descriptors (and also allows watching for multiple processes if desired)
    • when I switched ProcessHandle.waitFor implementation to use this persisted-state exit watcher, CI tests started failing immediately, hanging for 45 minutes and then getting killed
    • in case of BSD, the only difference was persisted kqueue
    • in case of Linux, it was epoll_create+epoll_ctl (persisted) and epoll_wait, instead of ppoll
    • if LLM is to be believed, GenericProcessWatcher and unit test calling waitFor would clash and one of them would block (it doesn't even matter which one)

so, while I can't state unequivocally that persisting epoll is definitely a problem, there's a non-zero probability that using it in this particular manner might be.

@LeeTibbert
Copy link
Contributor

LeeTibbert commentedNov 4, 2025
edited
Loading

I have spent at least two long, rested sessions examining this PR and have reached
as far as I can with static analysis. To go further, I would have to build it and insert
some probes: Who is actually doing what to whom?

I believe that I am looking a the most current code. Hard to tell for sure.

Let me speak plainly with the intent of helping the dialectic, not offending, but pick my
words carefully.

At the line-of-code level I saw an attention to detail that made it believably correct.
I also saw that areas of complexity in earlier drafts, possibly in other PRs had been
addressed. Very well crafted.

I believe that is is necessary to rise above the line-by-line and ask two questions
of grand tactics.

  • Is this code necessary to facilitate work on further PRs, say ones to address
    remaining disadvantages inWatcher code?

    If yes and asked to put my marker on red or black, I would demure and suggest
    an alternate reviewer.

    One concrete observation: I believe I understand the drive to centralize os process waiting & reaping.
    I'll report that out of respect for the author, I spent a significant amount of time in UnixProcessGen2
    trying to figure out what was a Watcher in the traditional sense and what were the methods
    with Watcher related names actually doing in that class. As usual, I hate to confess my limitations
    in front of my colleagues & betters.

    Another reviewer may not have a problem tracing that code.

  • Is this code any better than the code on, say November 1?

    I believe it makes more obvious solutions to a correct, lower cost Watcher more difficult.

    I can elaborate on that if someone can suggest the proper place. Condensing &
    foreshadowing a much longer discussion. With a tiny number of pids, who cares how the Watcher is implemented?
    Moving to small to huge, the fewer process the Watcher actually has to watch the better the
    latency of each process watched. One can do make rough estimates or measurements of expected code paths.
    With 100% 'Java9' 'never waitPid` processes, the Watcher becomes equivalent to "watch all processes".
    With 0% an 'easy' implementation may create the Watcher, but has no processes to watch.

    This is Open Source and a developer can always present a solution which rolls back these changes when
    an actual working PR is presented.

    At the very least those alternatives should be discussed and possibly some design studies done.
    I suspect that one such is in progress studying an event driven Watcher for macOS.

I do agree that the pid waiting & reaping code as of Nov 1 would benefit from improvement,
where for improvement is measured by both correctness and time for a competent person to
trace.

Copy link
ContributorAuthor

@kitbellewkitbellew left a comment

Choose a reason for hiding this comment

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

in addition to the line comment below, here is my take on the other comment, about design.

perhaps it would help if renamed this EventWatcher as something else, maybe that's the confusion. all it does is allows querying if some process (or the process, for single-pid watchers) has exited. then the process handle uses it to implement its waitFor methods. that's all.

the old bsdWaitForImpl has become EventWatcherFreeBSD.Single.waitAndReapSome, and the old linuxWaitForImpl is now EventWatcherLinux.Single.waitAndReapSome.

here are the next steps, if this ever gets merged:

  • unify Gen1 and Gen2:
    • implement any-process EventWatcher using waitpid(-1), for Gen1
    • move Gen2 construction complexity into a factory
    • now that we have a factory to create UnixProcess and an EventWatcher to monitor its completion, unify Gen1 and Gen2
  • watch multiple processes at once
    • implement a multi-process EventWatcher (persisted kqueue or epoll set with multiple pids), for Gen2
    • sincewe think that a persisted kqueue/epoll cannot be used with multiple waiters, use it exclusively in GenericProcessWatcher (where we are guaranteed to call one at a time)
    • possibly better than ppolling/keventing one process at a time

if (pidfd < 0) None else Some(pidfd)
}

/* We use ppoll here as an epoll descriptor may not be persisted; if multiple
Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know how epoll behaves internally, can go by two points:

  • one is what ChatGPT keeps telling me; I find that if the LLM persists despite constant challenges and disbelief, it tends to be right
    • for instance, it kept insisting that some code in MacOS's implementation ofkevent makesNOTE_EXITSTATUS completely unreliable, and I had to try it myself before running into problems and agreeing
    • in this case, it is firm in that both epoll_wait and kevent are not guaranteed to be woken up by the kernel if there are other waiters for the same event
  • two is this: this is not the last commit in my sequence, and there are a few more leading to an exit watcher which persists kqueue (bsd) and epoll (linux) descriptors (and also allows watching for multiple processes if desired)
    • when I switched ProcessHandle.waitFor implementation to use this persisted-state exit watcher, CI tests started failing immediately, hanging for 45 minutes and then getting killed
    • in case of BSD, the only difference was persisted kqueue
    • in case of Linux, it was epoll_create+epoll_ctl (persisted) and epoll_wait, instead of ppoll
    • if LLM is to be believed, GenericProcessWatcher and unit test calling waitFor would clash and one of them would block (it doesn't even matter which one)

so, while I can't state unequivocally that persisting epoll is definitely a problem, there's a non-zero probability that using it in this particular manner might be.

Expand `pidfd` field in UnixProcessGen2 to take a factory object whichinitializes an instance which can be used to query this process forcompletion.
@kitbellewkitbellew changed the titleEventWatcher: add lnx/bsd single-pid exit watchersExitChecker: add lnx/bsd single-pid exit watchersNov 5, 2025
@kitbellew
Copy link
ContributorAuthor

renamed as ExitChecker, to remove confusion with GenericProcessWatcher, as the watcher has an active thread while this new checker doesn't do anything until asked, so it provides ability to check for exit.

@LeeTibbert
Copy link
Contributor

It is nice to see the sketch of a development plan.

I think this dialogue is moving forward. To try to have some kind of order, let me
split my replies into Present and Future.

Present.

If you say all of this is going to change and soon, perhaps you might think of leaving this
as Draft while you implement those changes. That would leave the code in mainline in a
less complex state in case you get hit by a bus. It would also give time for some complexity
to go away or collapse into a form which can be more easily explained.

Yes, there is an overhead to holding lots of 'pending' files in current Work-In-Progress
files. People following the PR grumble about too many changes. In this case, from
what you describe the 'pending' files are the ones which would be changed in the WIP
in any case. That is, very little shrubbery.

@kitbellew
Copy link
ContributorAuthor

i must be in the minority who thinks that the proposed state is a bit clearer, with a few design patterns deployed to add clear division of labor, as well as remove several if-else and throw platform-dependent statements.

@LeeTibbert
Copy link
Contributor

LeeTibbert commentedNov 5, 2025
edited
Loading

| or instance, it kept insisting that some code in MacOS's implementation of kevent makes NOTE_EXITSTATUS completely
|unreliable, and I had to try it myself before running into problems and agreeing

I've encountered this statement before. It sounds like a macOS 10.n, where n is something like 5, issue.
It has been reported as fixed in 10.n+mumble. macOS is now at 16.n and SN CI now runs at macOS 15.

Agreed we need to be on the look out and probably exercise these.

On a closely related topic.

More and more in my reading aboutkevent,poll(), andppoll() I have been coming across
poorly described issues when closing a, say pidfd in one process which is being used in appoll() in another.
The latter may not get waken up. I have not tested this out, but it is rising in my "be sensitive to not introducing this bug'
design list.

If you are going where I think you are going, a single os waiter, this may not be a concern.

I think eventfd was specifically designed to wake up all waiters, so the close there may not
be an issue. Agreed, executing eventfd on macos might be difficult.

I mention the the 'pidfd' possible behavior because I had described allocating one and
using in both theProcess and theWatcher.

Somewhere down my interrupt stack and getting further down by the day, another
contributor and I are dealing with 'asynchronous close' not waking up processes
which are waiting in 'accept()' or 'connect()' when the relevant fd is closed in
a second process. A hack is in place to buy time. The situations sound very
familiar. The long term solution for Linux is probably setting eventfd on close.
A macOs solution is harder, probably similar to 'self-pipe trick' but avoiding signals.

os-lib also has a problem with hanging processes after doingdestroy()
in one process and then awaitFor(). Yes 0.5.9 problem, not this PR
but a faint suggestion that we may want to be careful withasynchronous close.

@LeeTibbert
Copy link
Contributor

LeeTibbert commentedNov 5, 2025
edited
Loading

Future

Code simplification

Unifying Gen1 and Gen2 to the greatest extent sensible is certainly a good goal.

  • I have been increasingly having the intrusive thought that perhaps all of this (good) collapse
    open the way to a 'clean sheet of paper' design. At least conceptually, the series of
    Evolutions may get us somewhat similar but may also trap us in a box canyon.
Process waiting architecture.

possibly better than ppolling/keventing one process at a time

I suggest this is the crux. I also think that the JVM specification does us no
favors here.

Let me describe two contrasting models and apply some math. Yes, I am skipping over
a discussion that should be here about 'on what dimensions/factors are we trying to optimize'.
And yes, this is more than a bit geeky.
I think that prior SN devo experiments in this area show that we need some form of a child process exit
Watcher to implement the JDK 9 'onExit' requirements.

For analysis lets identify two models or design architectures for how process reaping happens
between Watcher and Process threads. I use process reaping because the os process id slot
is the underlying shared resource and driver of events.

One model can be described as "Watcher does most of the work", say "Load the Watcher".
It does all waiting for notification of the exit of any of the Process it is watching. If it knew that the Process whose
child is being watched was waiting in a javalib 'waitFor()' it could set a condition variable or
use, on Linux, eventfd. Unfortunately, in the simple case, it can not know this, so it must
reap the now exited child process itself.

Another model can be described as "Processes do the majority of the waiting and reaping
while the Watcher handles 'no pending or eventual waitFor' cases". Say "Load the Processes".

I'll assume an event driven Watcher. The removal sequential scan of Processes being watched is
under active investigation in order to avoid a linear scan of potentially large lists and also sleep()s.

I think this is the key area needing both design studies an analysis.
In the simplest case, that leaves us with one Watcher thread which lives until that Runtime exits, one
Process thread, and one child thread. Now a watched child exits.

If there the parent is not in a waitFor, the Watcher reaps the process.

If the process is in some for of waitFor, there is a race to reap the child and
one must be careful about how one closes a, say, pidfd which might be being
used by the other.

I think advantage in reduced code complexity goes to the "Watcher does most of work" model
here, although the runtime costs are slightly lower when the Process wins the race. There
is no trip through the thread scheduler.

I will also note that probably 95+ of current and near term uses will be of
this simple case. Most cases in SN Process test prior to Fall 2025
fall into this description.

One Watcher, One Process, two waitFor

Now expand into the case where there is one Watcher, one Process, its child, and
two threads each calling waitFor on that process.

Now there are two chances that the slightly more runtime efficient process reaping will happen.

And one chance that the Watcher will have to spend time reaping the child. There are
no other Processes being monitored, so no one is delayed.

One Watcher, Two Process each with a waitFor.

For the sake of analysis, consider the case where the child process for each of the Processes
exits at the 'same' kernel time as seen by ppoll() etc.

Now there are two chances that the slightly more runtime efficient process reaping will happen.

And one chance that the Watcher will have to spend time reaping the children. Time spent
reaping the first, increases the latency in reporting the exit of the second child.
Granted that we are talking milliseconds here, the line of reasoning will progress.

If each Process itself has several threads in waitFor, the chances for a low latency
detection in the Process itself goes up.

Theoretical advantage to 'Load the processes" model.

One Watcher, Large N Process, each with possibly more than pending 'waitFor'

Consider an extreme or upper bound Pandemonium case. Will SN ever actually encounter this?
Probably but it is relatively easy to avoid it by initial design choice.

Now there are large N+ chances that the slightly more runtime efficient process reaping will happen.

And one chance that the Watcher will have to spend time reaping the children. If the Watcher wins
the race, it has to spend time reaping each child. For a quick analysis, one can do a guestimate
of the number of events arriving in a 'ppoll()' interval. It will probably be a Poisson distribution
weighted towards one but with a long right tail. What is the cumulative area under that tail, for some
cutoff point? It is reasonable, to me, to believe that with large enough N, some number, 5? 10?
processes will be delayed for more that the time it would take a standalone process to reap().

By far and away, advantage goes to the "load the Processes model"

Alternatives

A trial design study implementation may be able to remove the "Watcher reaps" and attendant
latency at large N by having the Watcher not reap but notify the relevant Process that a reaping
is needed. That means that something in the Process needs to be waiting for that notification
independent of any caller, or not, of a 'waitFor' on that instance. This probably means another
thread or Future and expanded resource use.

Measuring theory

Theory allows one to make informed guesses, but how do we measure the slow points of
whatever model we implement. Can we get numbers to help choose between rough implementations
of the models.

Summation

In the "single Watcher, single Process, single waitFor() or not" case doing
the "Load the Watcher" model with an os wait in one placemay simplify the code at the
cost of a guaranteed trip through the thread scheduler.

Amortized over current and foreseeable work loads, the "Load the Processes" is highly
likely to result in lower latency in responding to child exit events (greater responsiveness).
The cost is waiting in two code places and, in the limit, many runtime places.

The test of skill in either model, and probably in any related model, is if the irreducible
complexity of the subject domain can be tamed so that a competent devo can trace and
maintain it.

Granted that having a black belt is the price of entry, does one need a first degree black
belt or a tenth degree?

@LeeTibbert
Copy link
Contributor

| must be in the minority

As far as I can tell, there are only two voices so far, so equality, not minority.
My intent is honest discussion, possibly leading to iterative improvement.

I do not have either the desire or the standing to be Horatius Cocles, who
barred the bridge.

I do not doubt for a moment l that there is point goodness here which should be captured here and would
certainly like to discuss them over beverages someday.

@LeeTibbert
Copy link
Contributor

It is off-top from this PR but does pertain to the 'future work' sketch presented earlier to
this discussion.

I propose to do a private design study using the current (Nov 5) mainline code and
adapting it so that UnixProcessGen2 registers with the current "watch every Process"
Watcher only whenProcessHandle.onExit() has been called.

If that study fails CI or does not show a reduction of number of Processes
being watched, then I will know.

If shows what I think it will, I could submit a Draft PR for discussion.
My intent is that suchwould_not become a candidate for merge.

I want to stay out of your way whilst you are on such a productive roll.

Reducing the load on a Watcher using a sequential scan is just shy of essential.
It may buy us enough time to work on and qualify a proper event driven Watcher.

I believe that reducing the load on any eventual event driven Watcher will
also pay off; to a lesser but still worthwhile degree.

If the proposed change does work, it would be relatively easy to implement, I think
even in the "Load the Watcher' model.


I think the above may offer the greatest benefit for time to market.

An alternative would be for me to do an independent design study using current mainline
code of implementing first either of linux or macos, then the other one. The idea would be
to see what two independent developers experienced, especially in terms of os specific
features and quirks. Not to spend scarce developer time on duplicate effort all the way to merge PR, but to get
two readings of the land.

My intent would be that such a study never itself become a candidate for merge. There is too much
change likely to happen over the next few weeks. I also, as said before, want to stay out of your way
whilst you are on such a productive roll. Anything I discover or avoid through design might save you some thrashing.

Of course, I will be reading along as you post your efforts.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

1 more reviewer

@LeeTibbertLeeTibbertLeeTibbert left review comments

Reviewers whose approvals may not affect merge requirements

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants

@kitbellew@LeeTibbert

[8]ページ先頭

©2009-2025 Movatter.jp