@@ -217,7 +217,8 @@ case class RunnerTask(task:Task,
217
217
Utils .writeFileContent(jobOutputPath, ast_pp)
218
218
}
219
219
220
- private def writeSubmitBashScript (env : Map [String , WdlValue ]) : Unit = {
220
+ // Figure out if a docker image is specified. If so, return it as a string.
221
+ private def dockerImage (env : Map [String , WdlValue ]) : Option [String ] = {
221
222
def lookup (varName : String ) : WdlValue = {
222
223
env.get(varName) match {
223
224
case Some (x) => x
@@ -235,37 +236,16 @@ case class RunnerTask(task:Task,
235
236
}
236
237
// Figure out if docker is used. If so, it is specified by an
237
238
// expression that requires evaluation.
238
- val docker : Option [String ] =
239
- task.runtimeAttributes.attrs.get(" docker" ) match {
240
- case None => None
241
- case Some (expr) => Some (evalStringExpr(expr))
242
- }
243
- docker match {
244
- case None => ()
245
- case Some (imgName) =>
246
- // The user wants to use a docker container with the
247
- // image [imgName]. We implement this with dx-docker.
248
- // There may be corner cases where the image will run
249
- // into permission limitations due to security.
250
- //
251
- // Map the home directory into the container, so that
252
- // we can reach the result files, and upload them to
253
- // the platform.
254
- val DX_HOME = Utils .DX_HOME
255
- val dockerRunPath = getMetaDir().resolve(" script.submit" )
256
- val dockerRunScript =
257
- s """ |#!/bin/bash -ex
258
- |dx-docker run --entrypoint /bin/bash -v ${DX_HOME }: ${DX_HOME } ${imgName} $$ {HOME}/execution/meta/script """ .stripMargin.trim
259
- System .err.println(s " writing docker run script to ${dockerRunPath}" )
260
- Utils .writeFileContent(dockerRunPath, dockerRunScript)
261
- dockerRunPath.toFile.setExecutable(true )
239
+ task.runtimeAttributes.attrs.get(" docker" ) match {
240
+ case None => None
241
+ case Some (expr) => Some (evalStringExpr(expr))
262
242
}
263
243
}
264
244
265
245
// Each file marked "stream", is converted into a special fifo
266
246
// file on the instance.
267
247
private def handleStreamingFiles (inputs : Map [String , BValue ])
268
- : (String , String , Map [Declaration , WdlValue ]) = {
248
+ : (Option [( String , String )] , Map [String , BValue ]) = {
269
249
// A file that needs to be stream-downloaded.
270
250
// Make a named pipe, and stream the file from the platform to the pipe.
271
251
// Keep track of the download process. We need to ensure pipes have
@@ -281,55 +261,76 @@ case class RunnerTask(task:Task,
281
261
val bashSnippet : String =
282
262
s """ |mkfifo ${fifo.toString}
283
263
|dx cat ${dxFileId} > ${fifo.toString} &
284
- |download_stream_pids+=( $$ !)
285
264
| """ .stripMargin
286
265
(WdlSingleFile (fifo.toString), bashSnippet)
287
266
}
288
267
289
- val m : Map [Declaration , (WdlValue , String )] = inputs.map{
290
- case (_, BValue (_, _, None )) => throw new Exception (" Sanity" )
291
- case (_, BValue (wvl, wdlValue, Some (decl))) =>
292
- wdlValue match {
268
+ val m : Map [String , (String , BValue )] = inputs.map{
269
+ case (varName, BValue (wvl, wdlValue, declOpt)) =>
270
+ val (wdlValueRewrite,bashSnippet) = wdlValue match {
293
271
case WdlSingleFile (path) if wvl.attrs.stream =>
294
- decl -> mkfifo(wvl, path)
272
+ mkfifo(wvl, path)
295
273
case WdlOptionalValue (_,Some (WdlSingleFile (path))) if wvl.attrs.stream =>
296
- decl -> mkfifo(wvl, path)
274
+ mkfifo(wvl, path)
297
275
case _ =>
298
276
// everything else
299
- decl -> (wdlValue, " " )
277
+ (wdlValue," " )
300
278
}
301
- }
279
+ val bVal : BValue = BValue (wvl, wdlValueRewrite, declOpt)
280
+ varName -> (bashSnippet, bVal)
281
+ }.toMap
302
282
303
283
// set up all the named pipes
304
284
val snippets = m.collect{
305
- case (_, (_, bashSnippet)) if ! bashSnippet.isEmpty => bashSnippet
285
+ case (_, (bashSnippet,_ )) if ! bashSnippet.isEmpty => bashSnippet
306
286
}.toVector
307
- val bashProlog = (" download_stream_pids =()" +:
287
+ val bashProlog = (" background_pids =()" +:
308
288
snippets).mkString(" \n " )
309
289
310
- // Wait for all download processes to complete. It is legal
290
+ // Wait for all background processes to complete. It is legal
311
291
// for the user job to read only the beginning of the
312
292
// file. This causes the download streams to close
313
- // prematurely, which can be show up as an error. We need to tolerate this
314
- // case.
315
- val bashEpilog : String = " wait ${download_stream_pids[@]}"
316
- val inputsWithPipes = m.map{ case (decl, (wdlValue, _)) => decl -> wdlValue }.toMap
317
- (bashProlog, bashEpilog, inputsWithPipes)
293
+ // prematurely, which can be show up as an error. We need to
294
+ // tolerate this case.
295
+ val bashEpilog = " "
296
+ // "wait ${background_pids[@]}"
297
+ /* """|echo "robust wait for ${background_pids[@]}"
298
+ |for pid in ${background_pids[@]}; do
299
+ | while [[ ( -d /proc/$pid ) && ( -z `grep zombie /proc/$pid/status` ) ]]; do
300
+ | sleep 10
301
+ | echo "waiting for $pid"
302
+ | done
303
+ |done
304
+ |""".stripMargin.trim + "\n" */
305
+ val inputsWithPipes = m.map{ case (varName, (_,bValue)) => varName -> bValue }.toMap
306
+ val bashPrologEpilog =
307
+ if (fifoCount == 0 ) {
308
+ // No streaming files
309
+ None
310
+ } else {
311
+ // There are some streaming files
312
+ Some ((bashProlog, bashEpilog))
313
+ }
314
+ (bashPrologEpilog, inputsWithPipes)
318
315
}
319
316
320
- private def writeBashScript (inputs : Map [String , BValue ]) : Unit = {
317
+ // Write the core bash script into a file. In some cases, we
318
+ // need to run some shell setup statements before and after this
319
+ // script. Returns these as two strings (prolog, epilog).
320
+ private def writeBashScript (inputs : Map [String , BValue ],
321
+ bashPrologEpilog : Option [(String , String )]) : Unit = {
321
322
val metaDir = getMetaDir()
322
323
val scriptPath = metaDir.resolve(" script" )
323
324
val stdoutPath = metaDir.resolve(" stdout" )
324
325
val stderrPath = metaDir.resolve(" stderr" )
325
326
val rcPath = metaDir.resolve(" rc" )
326
327
327
- // deal with files
328
- val (bashProlog, bashEpilog, inputsWithPipes) = handleStreamingFiles(inputs)
329
-
330
328
// instantiate the command
331
- val taskCmd : String = task.instantiateCommand(inputsWithPipes, DxFunctions ).get
332
- val shellCmd = List (bashProlog, taskCmd, bashEpilog).mkString(" \n " )
329
+ val env : Map [Declaration , WdlValue ] = inputs.map {
330
+ case (_, BValue (_,wdlValue,Some (decl))) => decl -> wdlValue
331
+ case (_, BValue (varName,_,None )) => throw new Exception (" missing declaration" )
332
+ }.toMap
333
+ val shellCmd : String = task.instantiateCommand(env, DxFunctions ).get
333
334
334
335
// This is based on Cromwell code from
335
336
// [BackgroundAsyncJobExecutionActor.scala]. Generate a bash
@@ -349,12 +350,17 @@ case class RunnerTask(task:Task,
349
350
|echo 0 > ${rcPath}
350
351
| """ .stripMargin.trim + " \n "
351
352
} else {
353
+ val cdHome = s " cd ${Utils .DX_HOME }"
354
+ var cmdLines : List [String ] = bashPrologEpilog match {
355
+ case None =>
356
+ List (cdHome, shellCmd)
357
+ case Some ((bashProlog, bashEpilog)) =>
358
+ List (cdHome, bashProlog, shellCmd, bashEpilog)
359
+ }
360
+ val cmd = cmdLines.mkString(" \n " )
352
361
s """ |#!/bin/bash
353
362
|(
354
- |if [ -d ${Utils .DX_HOME } ]; then
355
- | cd ${Utils .DX_HOME }
356
- |fi
357
- | ${shellCmd}
363
+ | ${cmd}
358
364
|) \\
359
365
| > >( tee ${stdoutPath} ) \\
360
366
| 2> >( tee ${stderrPath} >&2 )
@@ -365,6 +371,40 @@ case class RunnerTask(task:Task,
365
371
Utils .writeFileContent(scriptPath, script)
366
372
}
367
373
374
+ private def writeDockerSubmitBashScript (env : Map [String , WdlValue ],
375
+ imgName : String ,
376
+ bashPrologEpilog : Option [(String , String )]) : Unit = {
377
+ // The user wants to use a docker container with the
378
+ // image [imgName]. We implement this with dx-docker.
379
+ // There may be corner cases where the image will run
380
+ // into permission limitations due to security.
381
+ //
382
+ // Map the home directory into the container, so that
383
+ // we can reach the result files, and upload them to
384
+ // the platform.
385
+ val DX_HOME = Utils .DX_HOME
386
+ val dockerCmd = s """ |dx-docker run --entrypoint /bin/bash
387
+ |-v ${DX_HOME }: ${DX_HOME }
388
+ | ${imgName}
389
+ | $$ {HOME}/execution/meta/script """ .stripMargin.replaceAll(" \n " , " " )
390
+ val dockerRunPath = getMetaDir().resolve(" script.submit" )
391
+ val dockerRunScript = bashPrologEpilog match {
392
+ case None =>
393
+ s """ |#!/bin/bash -ex
394
+ | ${dockerCmd}""" .stripMargin
395
+ case Some ((bashProlog, bashEpilog)) =>
396
+ List (" #!/bin/bash -ex" ,
397
+ bashProlog,
398
+ dockerCmd,
399
+ bashEpilog
400
+ ).mkString(" \n " )
401
+ }
402
+ System .err.println(s " writing docker run script to ${dockerRunPath}" )
403
+ Utils .writeFileContent(dockerRunPath,
404
+ dockerRunScript)
405
+ dockerRunPath.toFile.setExecutable(true )
406
+ }
407
+
368
408
// Calculate the input variables for the task, download the input files,
369
409
// and build a shell script to run the command.
370
410
def prolog (jobInputPath : Path ,
@@ -385,17 +425,25 @@ case class RunnerTask(task:Task,
385
425
}.toMap
386
426
387
427
// evaluate the top declarations
388
- val decls : Map [String , BValue ] = evalDeclarations(task.declarations, inputWvls)
389
-
390
- // Write shell script to a file. It will be executed by the dx-applet code
391
- writeBashScript(decls)
392
-
393
- // write the script that launches the shell script. It could be a docker
394
- // image.
395
- val env : Map [String , WdlValue ] = decls.map{
428
+ val inputs : Map [String , BValue ] = evalDeclarations(task.declarations, inputWvls)
429
+ val env : Map [String , WdlValue ] = inputs.map{
396
430
case (varName, BValue (_,wdlValue,_)) => varName -> wdlValue
397
431
}.toMap
398
- writeSubmitBashScript(env)
432
+ val docker = dockerImage(env)
433
+
434
+ // deal with files that need streaming
435
+ val (bashPrologEpilog, inputsWithPipes) = handleStreamingFiles(inputs)
436
+
437
+ // Write shell script to a file. It will be executed by the dx-applet code
438
+ docker match {
439
+ case None =>
440
+ writeBashScript(inputsWithPipes, bashPrologEpilog)
441
+ case Some (img) =>
442
+ // write a script that launches the actual command inside a docker image.
443
+ // Streamed files are set up before launching docker.
444
+ writeBashScript(inputsWithPipes, None )
445
+ writeDockerSubmitBashScript(env, img, bashPrologEpilog)
446
+ }
399
447
400
448
// serialize the environment, so we don't have to calculate it again in
401
449
// the epilog
0 commit comments