17
17
import static com .google .common .collect .ImmutableMap .toImmutableMap ;
18
18
import static com .google .common .truth .Truth .assertThat ;
19
19
import static java .nio .charset .StandardCharsets .UTF_8 ;
20
+ import static java .util .concurrent .TimeUnit .SECONDS ;
20
21
21
22
import com .google .common .collect .ImmutableList ;
22
23
import com .google .common .collect .ImmutableMap ;
31
32
import com .google .devtools .build .lib .exec .util .SpawnBuilder ;
32
33
import com .google .devtools .build .lib .sandbox .SandboxHelpers .SandboxInputs ;
33
34
import com .google .devtools .build .lib .testutil .Scratch ;
35
+ import com .google .devtools .build .lib .testutil .TestUtils ;
36
+ import com .google .devtools .build .lib .vfs .DigestHashFunction ;
34
37
import com .google .devtools .build .lib .vfs .Dirent ;
38
+ import com .google .devtools .build .lib .vfs .FileSystem ;
35
39
import com .google .devtools .build .lib .vfs .FileSystemUtils ;
36
40
import com .google .devtools .build .lib .vfs .Path ;
37
41
import com .google .devtools .build .lib .vfs .PathFragment ;
38
42
import com .google .devtools .build .lib .vfs .Symlinks ;
43
+ import com .google .devtools .build .lib .vfs .inmemoryfs .InMemoryFileSystem ;
39
44
import java .io .IOException ;
40
45
import java .util .Arrays ;
46
+ import java .util .concurrent .BrokenBarrierException ;
47
+ import java .util .concurrent .CyclicBarrier ;
48
+ import java .util .concurrent .ExecutorService ;
49
+ import java .util .concurrent .Executors ;
50
+ import java .util .concurrent .Future ;
51
+ import java .util .concurrent .Semaphore ;
41
52
import java .util .function .Function ;
53
+ import javax .annotation .Nullable ;
54
+ import org .junit .After ;
42
55
import org .junit .Before ;
43
56
import org .junit .Test ;
44
57
import org .junit .runner .RunWith ;
@@ -53,12 +66,23 @@ public class SandboxHelpersTest {
53
66
54
67
private final Scratch scratch = new Scratch ();
55
68
private Path execRoot ;
69
+ @ Nullable private ExecutorService executorToCleanup ;
56
70
57
71
@ Before
58
72
public void createExecRoot () throws IOException {
59
73
execRoot = scratch .dir ("/execRoot" );
60
74
}
61
75
76
+ @ After
77
+ public void shutdownExecutor () throws InterruptedException {
78
+ if (executorToCleanup == null ) {
79
+ return ;
80
+ }
81
+
82
+ executorToCleanup .shutdown ();
83
+ executorToCleanup .awaitTermination (TestUtils .WAIT_TIMEOUT_SECONDS , SECONDS );
84
+ }
85
+
62
86
@ Test
63
87
public void processInputFiles_materializesParamFile () throws Exception {
64
88
SandboxHelpers sandboxHelpers = new SandboxHelpers (/*delayVirtualInputMaterialization=*/ false );
@@ -150,6 +174,56 @@ public void sandboxInputMaterializeVirtualInputs_delayMaterialization_writesCorr
150
174
assertThat (sandboxToolFile .isExecutable ()).isTrue ();
151
175
}
152
176
177
+ /**
178
+ * Test simulating a scenario when 2 parallel writes of the same virtual input both complete write
179
+ * of the temp file and then proceed with post-processing steps one-by-one.
180
+ */
181
+ @ Test
182
+ public void sandboxInputMaterializeVirtualInput_parallelWritesForSameInput_writesCorrectFile ()
183
+ throws Exception {
184
+ VirtualActionInput input = ActionsTestUtil .createVirtualActionInput ("file" , "hello" );
185
+ executorToCleanup = Executors .newSingleThreadExecutor ();
186
+ CyclicBarrier bothWroteTempFile = new CyclicBarrier (2 );
187
+ Semaphore finishProcessingSemaphore = new Semaphore (1 );
188
+ FileSystem customFs =
189
+ new InMemoryFileSystem (DigestHashFunction .SHA1 ) {
190
+ @ Override
191
+ protected void setExecutable (Path path , boolean executable ) throws IOException {
192
+ try {
193
+ bothWroteTempFile .await ();
194
+ finishProcessingSemaphore .acquire ();
195
+ } catch (BrokenBarrierException | InterruptedException e ) {
196
+ throw new IllegalArgumentException (e );
197
+ }
198
+ super .setExecutable (path , executable );
199
+ }
200
+ };
201
+ Scratch customScratch = new Scratch (customFs );
202
+ Path customExecRoot = customScratch .dir ("/execroot" );
203
+ SandboxHelpers sandboxHelpers = new SandboxHelpers (/*delayVirtualInputMaterialization=*/ false );
204
+
205
+ Future <?> future =
206
+ executorToCleanup .submit (
207
+ () -> {
208
+ try {
209
+ sandboxHelpers .processInputFiles (
210
+ inputMap (input ), SPAWN , EMPTY_EXPANDER , customExecRoot );
211
+ finishProcessingSemaphore .release ();
212
+ } catch (IOException e ) {
213
+ throw new IllegalArgumentException (e );
214
+ }
215
+ });
216
+ sandboxHelpers .processInputFiles (inputMap (input ), SPAWN , EMPTY_EXPANDER , customExecRoot );
217
+ finishProcessingSemaphore .release ();
218
+ future .get ();
219
+
220
+ assertThat (customExecRoot .readdir (Symlinks .NOFOLLOW ))
221
+ .containsExactly (new Dirent ("file" , Dirent .Type .FILE ));
222
+ Path outputFile = customExecRoot .getChild ("file" );
223
+ assertThat (FileSystemUtils .readLines (outputFile , UTF_8 )).containsExactly ("hello" );
224
+ assertThat (outputFile .isExecutable ()).isTrue ();
225
+ }
226
+
153
227
private static ImmutableMap <PathFragment , ActionInput > inputMap (ActionInput ... inputs ) {
154
228
return Arrays .stream (inputs )
155
229
.collect (toImmutableMap (ActionInput ::getExecPath , Function .identity ()));
0 commit comments