Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move SemaphoreStep states into a single map to try to simplify the implementation #260

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

dwnusbaum
Copy link
Member

@dwnusbaum dwnusbaum commented Mar 28, 2024

See #258 (comment).

Personally, I feel like this is still pretty comparable to #258 in terms of complexity. It seems a little better in terms of synchronization complexity, but the state transition complexity seems about the same or worse. The issue is that the four maps did not represent distinct non-overlapping states and so it is still not that clear what is going on in the new code. If we are purely going for simplicity, something like #259 would be my vote, then #258, then this PR.

If we want to go with this approach, I think this PR would need a PCT run because it seems possible that is accidentally broke some typical use case.

Copy link
Member

@jglick jglick left a comment

Choose a reason for hiding this comment

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

Not sure the details of the states are right, but the overall structure seems clearer to me.

sync = false;
}
synchronized (s) {
s.started.add(k);
// Even if we completed immediately, set the state to StartedState so waitForSuccess knows to stop waiting.
Copy link
Member

Choose a reason for hiding this comment

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

Why? I would expect the wait to just wait for the key to be present, with any value.

Also did you mean

Suggested change
// Even if we completed immediately, set the state to StartedState so waitForSuccess knows to stop waiting.
// Even if we completed immediately, set the state to StartedState so waitForStart knows to stop waiting.

?

Copy link
Member Author

@dwnusbaum dwnusbaum Mar 29, 2024

Choose a reason for hiding this comment

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

Because otherwise if you call SemaphoreStep.success or SemaphoreStep.failure and then SemaphoreStep.waitForStart without even running a Pipeline that uses the semaphore step, waitForStart will complete immediately.

EDIT: In practice this should not really happen, but if you had a complex test with multiple threads, it's possible, and the old code did not have that behavior, and it doesn't really seem like a behavior that we would want to introduce now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Basically, to really get this right, there needs to be at least 5 states, and we need to add a few more transitions that currently do not exist:

  • No entry in the map
  • ImmediateFailure (the step will fail, if/when it starts)
  • ImmediateSuccess (the step will succeed, if/when it starts)
  • Started/Waiting/Blocked (the step has started, and has not yet completed)
  • Finished (the step has finished, with some result) (does not currently exist, we'd need to add various transitions to this state)

And I do not think that we can introduce the Finished state without also introducing various behavior changes in edge cases that might affect existing tests. We'd definitely need a PCT run.

Copy link
Member Author

Choose a reason for hiding this comment

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

7ffaf48 introduces FinishedState, which I think makes things clearer.

@jglick
Copy link
Member

jglick commented Mar 29, 2024

this PR would need a PCT run

We should do a PCT run for any proposed change to this class. (jenkinsci/bom and/or @cloudbees internal)

@@ -108,12 +108,15 @@ public static void success(String k, Object returnValue) {
StepContext c;
synchronized (s) {
KeyState keyState = s.keyStates.get(k);
if (!(keyState instanceof StartedState)) {
if (keyState == null || keyState instanceof WillSucceedState || keyState instanceof WillFailState) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe introduce a common supertype WillFinishState.

return;
} else if (keyState instanceof FinishedState) {
throw new IllegalStateException(k + " already finished");
Copy link
Member Author

Choose a reason for hiding this comment

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

This breaks tests. In particular at least workflow-step-api / org.jenkinsci.plugins.workflow.steps.GeneralNonBlockingStepExecutionTest.stop and workflow-cps / org.jenkinsci.plugins.workflow.cps.CpsFlowExecutionTest.stepsAreStoppedWhenCpsVmExecutorServiceHandlesUncaughtException. I'll take a look.

Copy link
Member Author

@dwnusbaum dwnusbaum Mar 29, 2024

Choose a reason for hiding this comment

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

GeneralNonBlockingStepExecutionTest.stop I think just has a redundant call to SemaphoreStep.success that could be removed, but for compatibility we could replace this exception with just a logged warning.

CpsFlowExecutionTest.stepsAreStoppedWhenCpsVmExecutorServiceHandlesUncaughtException is a little unique, basically the test was using semaphore as a way to call StepContext.onSuccess after a Pipeline dies. To preserve that test's behavior without changes, we'd have to make FinishedState keep the serialized context around. Probably clearer to update that test to use a custom step instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants