Comments (6)
Thanks for creating the issue ;)
Yet it is unclear if
addLabels
fails if the label is already present. If not, you could end up having the multiple executors executing step 1.
I don't know if addLabels
would fail but I don't think it matters. Indeed, Autorebase won't attempt to rebase PRs flagged with the autorebasing
label at all:
and
autorebase/packages/autorebase/src/autorebase.js
Lines 129 to 132 in 25f6af4
The test for it is:
autorebase/packages/autorebase/tests/index.test.js
Lines 333 to 334 in 25f6af4
Do you still think there is an issue with this behavior?
from autorebase.
Hello @tibdex
sorry for not answering earlier. The mail notif gets lost into my spams.
As you mentioned, you may have two processes running this app at the same time, so you have to be careful about concurrency. The issue is that you fetch the list of PRs and their statuses once for all, and then process them, so they can both share the same initial state and decide to execute the same operations.
Warning: The following will be a bit long.
Let's assume that you have only 1 PR, numbered p.
Two processes start concurrently.
P1 fetches the PRs and their states:
autorebase/packages/autorebase/src/autorebase.js
Lines 210 to 214 in 78c0b6e
Then P2 executes the same piece of code.
At this point, both P1 and P2 see the same state for PR p.
Let's assume that the state is behind and need rebasing.
P2 executes
#getPullRequestToRebaseOrMerge
and is ready to rebase PR p.Then P1 runs the same function, gets PR p to rebase.
Both processes will call
addLabels
and they will both set the labels to autorebasing
. But none of them can detect that they are both performing the same action.So they will both attempt to rebase, and only one of them will succeed.
And it may get a bit worse here.
Assuming that P1 succeeds in rebasing, and P2 fails. P2 executes the finally block first:
autorebase/packages/autorebase/src/autorebase.js
Lines 165 to 173 in 78c0b6e
and correctly removes the label
autorebasing
and leaves.Then P1 comes and also tries to remove the same label. As the label can only be present once, this second call fails - for returning a 404. Although the job is done and there are no more actions after that, it results in an error in your app.
To avoid that, I suggest adding the label autorebasing
and then remove the label autorebase
. As only one process can remove the label, we can ensure that only one process is rebasing.
from autorebase.
I am trying to write a unit test with an object mocking octokit API to illustrate the issue, but I have trouble running this single test, and debugging it. I am not used to jest yet :(.
from autorebase.
Ok, I found a way to write my tests. It is the first commit of the PR I created - #7. The second commit offers a solution.
Cheers
from autorebase.
Thanks for the clear explanations.
As the label can only be present once, this second call fails - for returning a 404
Indeed, you found the lock I was looking for 👏.
To avoid that, I suggest adding the label
autorebasing
and then remove the labelautorebase
.
This made me think of a simpler solution. We could stop using the autorebasing
label altogether and simply use the autorebase
label as a lock. Here is how it would work:
There is only 1 PR behind labeled with autorebase
.
P1 and P2 fetches the PRs and their states.
P2 executes #getPullRequestToRebaseOrMerge
and is ready to rebase the PR.
Then P1 runs the same function, decides to rebase the PR too.
Both processes will attempt to remove the autorebase
label but only one will succeed. Let's say P1 succeeds.
P2, having failed to remove the autorebase
label, aborts and exits with type: "nop"
.
P1 goes on to rebase the PR. Finally, it adds the autorebase
label back to the PR.
I think it works fine and I like the fact that we don't even need the extra internal autorebasing
label anymore.
We could change the test suite like this:
diff --git a/packages/autorebase/tests/index.test.js b/packages/autorebase/tests/index.test.js
index 40ee7a8..25d9b9f 100644
--- a/packages/autorebase/tests/index.test.js
+++ b/packages/autorebase/tests/index.test.js
@@ -331,35 +331,27 @@ describe("rebasing label acts as a lock", () => {
});
test(
- "pull request is not rebased when it already has the rebasing label",
+ "concurrent instances of autorebase lead to a single rebase attempt",
async () => {
- await octokit.issues.addLabels({
- labels: [rebasingLabel],
+ const concurrentAutorebaseAttempts = await Promise.all(
+ Array(2)
+ .fill(() =>
+ autorebase({
+ octokit,
+ options,
+ owner,
+ repo,
+ })
+ )
+ .map(attemptRebase => attemptRebase())
+ );
+
+ // Check that only one instance actually attempted to rebase the pull request.
+ expect(concurrentAutorebaseAttempts).toContainEqual({ type: "nop" });
+ expect(concurrentAutorebaseAttempts).toContainEqual({
number,
- owner,
- repo,
- });
- const firstAutorebase = await autorebase({
- octokit,
- options,
- owner,
- repo,
- });
- expect(firstAutorebase).toEqual({ type: "nop" });
-
- await octokit.issues.removeLabel({
- name: rebasingLabel,
- number,
- owner,
- repo,
- });
- const secondAutorebase = await autorebase({
- octokit,
- options,
- owner,
- repo,
+ type: "rebase",
});
- expect(secondAutorebase).toEqual({ number, type: "rebase" });
await createSuccessStatus({
octokit,
If you consider the test to not be thorough enough, we could use an interception strategy instead of resorting to mocking octokit
. Something like that:
diff --git a/packages/autorebase/src/autorebase.js b/packages/autorebase/src/autorebase.js
index 1b4a5d9..c29c31e 100644
--- a/packages/autorebase/src/autorebase.js
+++ b/packages/autorebase/src/autorebase.js
@@ -196,11 +196,14 @@ const rebase = async ({ number, octokit, owner, repo }) => {
};
const autorebase = async ({
+ // Should only be used in tests.
+ _intercept = () => Promise.resolve(),
octokit,
options = { label: "autorebase" },
owner,
repo,
}: {
+ _intercept?: () => Promise<void>,
octokit: Github,
options?: Options,
owner: RepoOwner,
@@ -217,6 +220,7 @@ const autorebase = async ({
pullRequestToMerge,
pullRequestToRebase,
} = getPullRequestToRebaseOrMerge(pullRequests);
+ await _intercept();
debug("pull requests", {
pullRequests,
pullRequestToMerge,
And we would use _intercept
in the "concurrent instances of autorebase lead to a single rebase attempt" test to have the two instances wait for each other before actually calling:
I already do something similar for testing the atomicity of github-cherry-pick
and github-rebase
.
What do you think?
from autorebase.
Sorry for the delay in the answer, I was on holiday 😄
I don't see any objection to your suggestion. I agree that limiting the number of labels any user needs to create is better. Having autorebasing
was clearer for debugging purposes but not necessary at all.
I will update my PR accordingly.
Same about the intercept method. If you already use it somewhere else, why not adding it? As you are only using objects as arguments to your functions, it is easy to provide any additional hook.
I tell you when the PR is ready. BTW, I haven't read anything in the README about the test. Do I have to create a repo of my own to run the tests ? Where do I pick all the required details (app id, ...) ?
from autorebase.
Related Issues (15)
- Describe whether the service is free or not and how long it will stay around HOT 1
- Working with CI HOT 2
- Clarify how / why labels are added or removed HOT 3
- Adding the label does not trigger autorebase HOT 8
- What if I want the autorebase feature but not the automerge ? HOT 11
- Question concerning usage of this app HOT 8
- Optional rebase + merge --no-ff HOT 4
- Rebase failed: Not Found HOT 3
- Merge strategies: How to squash'n'merge by default but also allow rebase HOT 1
- When autorebase does it's job, development status in Jira is outdated for a while HOT 1
- Missing instructions for running tests HOT 1
- Github Rate Limit HOT 1
- Public instance down HOT 3
- Explain the alternatives better in the README
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from autorebase.