ocaml-omake / omake

The new home of OMake - docs, downloads, mailing list etc. see:
http://projects.camlcity.org/projects/omake.html
GNU General Public License v2.0
67 stars 25 forks source link

Function call implicitly serializes build #116

Open cspiel opened 5 years ago

cspiel commented 5 years ago

On my 4-way Linux box calling an OMake function in a rule body serializes an otherwise parallel build. The following OMakefile reproduces the behavior.

.LANGUAGE: program

iota(n) =
        iter(a, x) =
                if ge(x, n)
                        return a
                else
                        return iter(array(a, x), add(x, 1))
        init[] =
        return iter(init, int(0))

write_script_file(a_filename) =
        fprint(a_filename, $'''#! /bin/sh

sleep 1
cp $1 $2
''')

filenames = addprefix($'a', iota(8))

source_ext = $'.s'
direct_target_ext = $'.d'
procedure_target_ext = $'.p'
function_target_ext = $'.f'

sources = addsuffix(source_ext, filenames)
direct_targets = addsuffix(direct_target_ext, filenames)
procedure_targets = addsuffix(procedure_target_ext, filenames)
function_targets = addsuffix(function_target_ext, filenames)

.LANGUAGE: make

lock_file = lock
script_file = sleep-and-copy.sh
command = flock --no-fork --timeout=0.25 --verbose $(lock_file) /bin/sh $(script_file)

.PHONY: gen
.DEFAULT: gen
gen:
        touch $(sources)
        write_script_file($(script_file))

.PHONY: clean
clean:
        rm -f *$(direct_target_ext)
        rm -f *$(function_target_ext)
        rm -f *$(procedure_target_ext)
        rm -f $(lock_file)

.PHONY: distclean
distclean: clean
        rm -f *$(source_ext)
        rm -f $(script_file)

.PHONY: direct
direct: $(direct_targets)

.PHONY: proc
proc: $(procedure_targets)

.PHONY: func
func: $(function_targets)

##  omake -j1 direct           # Always works ok because of serial execution.
##  omake -j9 direct           # Intentionally fails, which proves the base
                               # case, i.e. the mutex correctly catches for the
                               # tests proper.

%$(direct_target_ext): %$(source_ext)
        $(command) $< $@

f(a_source, a_target) =
        $(command) $(a_source) $(a_target)
        return                 # This `return' statement is necessary to expose
                               # the quirk.  Without it, omake(1) complains
                               # `command not found in PATH: ' when using
                               # function-call syntax (but not procedure-call
                               # syntax).

##  Using procedure-call syntax `function(arg...)' makes the function
##  call *not* interfere with parallelization is expected; it mimics
##  direct execution.
##
##  omake -j1 proc             # Again, always works ok because of serial
##                             # execution.
##  omake -j9 proc             # Fails as expected.

%$(procedure_target_ext): %$(source_ext)
        f($<, $@)              # Respects parallel execution.

##  Using function-call syntax `$(function arg ...)' blocks
##  parallelization.
##
##  omake -j1 func             # Serially builds the targets as its `direct'
##                             # equivalent.
##  omake -j9 func             # Implicitly serializes the build, thus the
                               # build succeeds, where it should fail instead.

%$(function_target_ext): %$(source_ext)
        $(f $<, $@)            # Ignore any returned value from `f'?

To verify a truly parallel command execution at shell level, we call flock with a command that certainly takes longer than the timeout of flock. Use omake gen to create a starting configuration.

Phony target direct evaluates command without any function call interposed, so if everything is set up correctly

omake -j1 direct

builds all .d files and

omake -j9 direct

fails because the mutex ("lock") cannot be acquired.

Target func does not call command, but function f, which in turn calls command. Here, both

omake -j1 func

and

omake -j9 func

succeed and build all .f files.

Curiously, in our example, phony target proc, which calls function f with "procedure call" syntax

f(arg...)

in contrary to "function call" syntax of func

$(f arg...)

duplicates the findings of direct, this is, does not interfere with parallel execution, as expected.