Skip to content

Commit 2466e47

Browse files
committed
fix: make sure node_modules dir is exclusively created
Refs: #2462
1 parent e4b8f30 commit 2466e47

File tree

2 files changed

+79
-9
lines changed

2 files changed

+79
-9
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2025 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.npm;
17+
18+
import java.io.File;
19+
import java.util.Objects;
20+
import java.util.concurrent.ConcurrentHashMap;
21+
import java.util.concurrent.locks.Lock;
22+
import java.util.concurrent.locks.ReentrantLock;
23+
24+
import javax.annotation.Nonnull;
25+
26+
import com.diffplug.spotless.ThrowingEx;
27+
28+
interface ExclusiveFolderAccess {
29+
30+
static ExclusiveFolderAccess forFolder(@Nonnull File folder) {
31+
return forFolder(folder.getAbsolutePath());
32+
}
33+
34+
static ExclusiveFolderAccess forFolder(@Nonnull String path) {
35+
return new ExclusiveFolderAccessSharedMutex(Objects.requireNonNull(path));
36+
}
37+
38+
void runExclusively(ThrowingEx.Runnable runnable);
39+
40+
class ExclusiveFolderAccessSharedMutex implements ExclusiveFolderAccess {
41+
42+
private static final ConcurrentHashMap<String, Lock> mutexes = new ConcurrentHashMap<>();
43+
44+
private final String path;
45+
46+
private ExclusiveFolderAccessSharedMutex(@Nonnull String path) {
47+
this.path = Objects.requireNonNull(path);
48+
}
49+
50+
private Lock getMutex() {
51+
return mutexes.computeIfAbsent(path, k -> new ReentrantLock());
52+
}
53+
54+
@Override
55+
public void runExclusively(ThrowingEx.Runnable runnable) {
56+
final Lock lock = getMutex();
57+
try {
58+
lock.lock();
59+
runnable.run();
60+
} catch (Exception e) {
61+
throw ThrowingEx.asRuntime(e);
62+
} finally {
63+
lock.unlock();
64+
}
65+
}
66+
}
67+
}

lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2024 DiffPlug
2+
* Copyright 2016-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -87,14 +87,17 @@ protected void prepareNodeServer() throws IOException {
8787
}
8888

8989
protected void assertNodeServerDirReady() throws IOException {
90-
if (needsPrepareNodeServerLayout()) {
91-
// reinstall if missing
92-
prepareNodeServerLayout();
93-
}
94-
if (needsPrepareNodeServer()) {
95-
// run npm install if node_modules is missing
96-
prepareNodeServer();
97-
}
90+
ExclusiveFolderAccess.forFolder(nodeServerLayout.nodeModulesDir())
91+
.runExclusively(() -> {
92+
if (needsPrepareNodeServerLayout()) {
93+
// reinstall if missing
94+
prepareNodeServerLayout();
95+
}
96+
if (needsPrepareNodeServer()) {
97+
// run npm install if node_modules is missing
98+
prepareNodeServer();
99+
}
100+
});
98101
}
99102

100103
protected boolean needsPrepareNodeServer() {

0 commit comments

Comments
 (0)