Bug 10785

Summary: shell logging function appears to break task output
Product: [Build System, Metadata & Runtime] BitBake Reporter: Paul Eggleton <bluelightning>
Component: bitbakeAssignee: Juro Bystricky <juro.bystricky>
Status: RESOLVED FIXED QA Contact:
Severity: normal    
Priority: Medium+ CC: poky.bs.watcher, poky.watcher, randy.macleod, ross.burton, sgw
Version: 2.2   
Target Milestone: 2.4 M2   
Hardware: x86   
OS: Multiple   
Whiteboard:
OS type for building Yocto: --- Type of Regression: ---
Verified: Documentation change: No (bug/feature does not impact docs)
Attachments:
Description Flags
patch that passes all the tests.... none

Description Paul Eggleton 2016-12-12 02:34:16 UTC
I've just noticed some bizarre behaviour with the shell logging functions i.e. bbwarn/bbdebug/bberror. If you add the following to a recipe:

--------------- snip ---------------
addtask test1
do_test1[nostamp] = "1"

do_test1() {
    bbdebug 1 "breakage"
    echo "this will not be printed"
    printf "nor this\n"
}
--------------- snip ---------------

Then run bitbake -c test1 recipename, the task log contains this:

--------------- snip ---------------
DEBUG: Executing shell function do_test1
DEBUG: breakage
DEBUG: Shell function do_test1 finished
--------------- snip ---------------

i.e. for some reason the echo and printf lines don't make it to the log. If you try one of: commenting out the bbdebug line, adding "ls -l" or even "ls -l > /dev/null" at the end, or commenting out the bb.debug() call in readfifo() in lib/bb/build.py then the echo/printf lines do get printed. However no amount of additional echo lines appear to make a difference. An "echo something > /tmp/testfile" added to the end does actually create a /tmp/testfile with "something" in it, so the subsequent commands are being executed, it's just that the console output is apparently broken.

(I found this issue on master, but krogoth seems similarly afflicted.)
Comment 1 Paul Eggleton 2016-12-12 02:50:08 UTC
In fact it looks like this has been broken since jethro, which probably means since the fix to pipe shell logging messages to the console was implemented.
Comment 2 Paul Eggleton 2016-12-22 03:42:48 UTC
Perhaps this is output not being flushed before the task log is closed - though why using the python output function would trigger that I'm not sure. (When I tried bitbake -v I saw the same sort of behaviour, so it's not just the log file).
Comment 3 Paul Eggleton 2017-06-06 11:15:08 UTC
Juro - I'm a bit stumped by this - could you take a look?
Comment 4 Juro Bystricky 2017-06-06 15:44:52 UTC
(In reply to comment #3)
> Juro - I'm a bit stumped by this - could you take a look?

Hi Paul, nice mystery. Good thing it is reproducible (unlike some other log-related bugs).
Comment 5 Juro Bystricky 2017-06-06 17:43:27 UTC
This is my observation with the master:
meta-yocto-bsp    = "HEAD:02417b149623c3042c257d1c1074f915dc02b7c7"


$ bitbake <recipe> -cdo_test1

sometimes I get the log busted as advertised:
DEBUG: SITE files ['endian-little', 'bit-32', 'ix86-common', 'common-linux', 'common-glibc', 'i586-linux', 'common']
DEBUG: Executing shell function do_test1
DEBUG: breakage
DEBUG: Shell function do_test1 finished

but most of the time the log is correct:

DEBUG: SITE files ['endian-little', 'bit-32', 'ix86-common', 'common-linux', 'common-glibc', 'i586-linux', 'common']
DEBUG: Executing shell function do_test1
DEBUG: breakage
this will not be printed
nor this
DEBUG: Shell function do_test1 finished
Comment 6 Juro Bystricky 2017-06-06 20:07:13 UTC
More observations:
The following task:

do_test3() {
    bbdebug 1 "breakage1"
    echo "this will not be printed"
    printf "nor this\n"
    bbdebug 1 "breakage2"
}

Generates the output:

...
DEBUG: Executing shell function do_test3
DEBUG: breakage1
DEBUG: breakage2
this will not be printed
nor this
DEBUG: Shell function do_test3 finished


Note the messages are all there but in wrong order. This, I believe is 100% reproducible.
Comment 7 Juro Bystricky 2017-06-06 23:39:38 UTC
So this patch fixes the out-of-order-messages and possibly the original bug as well. (I guess I will need to write/run some kind of test program)

diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py
index 0d0100a..d3bf2d5 100644
--- a/bitbake/lib/bb/build.py
+++ b/bitbake/lib/bb/build.py
@@ -426,6 +426,7 @@ exit $ret
                     bb.debug(level, value)
                 else:
                     bb.warn("Unrecognised command '%s' on FIFO" % cmd)
+                logfile.flush()
                 fifobuffer = nextmsg
             else:
                 break
@@ -435,13 +436,14 @@ exit $ret
     if os.path.exists(fifopath):
         os.unlink(fifopath)
     os.mkfifo(fifopath)
+    from subprocess import PIPE
     with open(fifopath, 'r+b', buffering=0) as fifo:
         try:
             bb.debug(2, "Executing shell function %s" % func)
 
             try:
                 with open(os.devnull, 'r+') as stdin:
-                    bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)])
+                    bb.process.run(cmd, shell=False, stdin=stdin, stdout=PIPE, log=logfile, extrafiles=[(fifo,readfifo)])
             except bb.process.CmdError:
                 logfn = d.getVar('BB_LOGFILE')
                 raise FuncFailed(func, logfn)
Comment 8 Juro Bystricky 2017-06-07 17:24:59 UTC
Spoke too soon.
A repeated test program still detects both missing log entries and entries in incorrect order. So back to the drawing board.
Comment 9 Juro Bystricky 2017-06-08 01:11:25 UTC
OK, so I think I got it right this time. I will test it overnight, testing various combinations of shell outputs/fifo log writes for completeness and correct order. So far so good.

diff --git a/bitbake/lib/bb/process.py b/bitbake/lib/bb/process.py
index a4a5599..138f908 100644
--- a/bitbake/lib/bb/process.py
+++ b/bitbake/lib/bb/process.py
@@ -81,7 +81,6 @@ def _logged_communicate(pipe, log, input, extrafiles):
         rin.append(pipe.stderr)
     for fobj, _ in extrafiles:
         bb.utils.nonblockingfd(fobj.fileno())
-        rin.append(fobj)
 
     def readextras(selected):
         for fobj, func in extrafiles:
@@ -105,6 +104,8 @@ def _logged_communicate(pipe, log, input, extrafiles):
                 if e.errno != errno.EINTR:
                     raise
 
+            readextras([fobj for fobj, _ in extrafiles])
+
             if pipe.stdout in r:
                 data = stdoutbuf + pipe.stdout.read()
                 if data is not None and len(data) > 0:
@@ -126,13 +127,9 @@ def _logged_communicate(pipe, log, input, extrafiles):
                         stderrbuf = b""
                     except UnicodeDecodeError:
                         stderrbuf = data
-
-            readextras(r)
-
+            log.flush()
     finally:    
-        log.flush()
-
-    readextras([fobj for fobj, _ in extrafiles])
+        pass
 
     if pipe.stdout is not None:
         pipe.stdout.close()
Comment 10 Juro Bystricky 2017-06-08 15:40:42 UTC
Extended testing passed. I tested these cases:

do_test1() {
    bbdebug 1 "breakage"
    echo "this will not be printed"
    printf "nor this\n"
}


do_test2() {
    echo "this will not be printed"
    bbdebug 1 "breakage"
    printf "nor this\n"
}


do_test3() {
    echo "line1"
    bbdebug 1 "breakage1 line2"
    echo "this will not be printed (line3)"
    printf "nor this (line4)\n"
    bbdebug 1 "breakage2 line5"
}


do_test4() {
    bbdebug 1 "breakage1 line2"
    bbdebug 1 "breakage2 line2"
}

Although all the tests passed, by looking a the logging code, I am not sure we can guarantee a "correct" order of log entries. There is a round-robin loop for merging stdout, fifo (bbdebug uses fifo) and stderr messages into the log file.
Comment 11 Paul Eggleton 2017-06-08 15:44:24 UTC
Great work! Personally potential ordering anomalies don't bother me nearly as much as losing entire messages, so if that's the only remaining issue I'd say it's good enough.
Comment 12 Juro Bystricky 2017-06-08 23:37:41 UTC
(In reply to comment #11)
> Great work! Personally potential ordering anomalies don't bother me nearly
> as much as losing entire messages, so if that's the only remaining issue I'd
> say it's good enough.

Well, apparently third time is the charm. I can actually explain what went wrong.
Basically we have this code (proccess.py):

def _logged_communicate(pipe, log, input, extrafiles):
   <do_some_stuff>
   while ProcessOpen
       log fifo(s)
       log stdout 
       log stderr

   log_fifo(s)

(Note after Process is closed we make effort to drain/log fifos but not stdout/stderr)

What can happen is that Process is closed before the code reaches 
the "while" loop and hence the pipes do not get a chance to be logged/drained. 
Or (most likely) the process is closed somewhere in the loop and some of the pipes may have still data in them.

So the fix is this:

   while ProcessOpen
       log fifo(s)
       log stdout 
       log stderr

   # Drain ALL pipes after process closed
   log_fifo()
   log stdout
   log stderr

Which can be reformatted as:

    def read_all_pipes:
       log fifo(s)
       log stdout 
       log stderr

    while ProcessOpen
       read_all_pipes

   # Drain ALL pipes after process closed
   read_all_pipes
    
The previous patch worked very well in tests, but did not address the root cause of the problem. 
So I am testing the v3 of the patch, with additional tests. I'll let it run overnight and if there are no problems I'll send the patch to the mailing list.
Comment 13 Juro Bystricky 2017-06-09 23:16:32 UTC
Created attachment 3809 [details]
patch that passes all the tests....