Mac App Broken-Pipe Work-Around (Aug-2017)

This page provides more details on the August 2017 broken-pipe-error work-arounds applied to the Mac app versions of Frigcal and PyMailGUI only. It's mostly intended as an example for developers facing similar issues. For a brief summary of this fix, see the main program-updates page.

If you are a user of the Mac app versions of these programs, please upgrade to the August 14, 2017 (or later) versions to avoid potential problems. The fix is also present in the latest versions of source-code packages for these two programs, but as example only; the issue occurs only in apps.

Context

By design, Frigcal and PyMailGUI both use a standard parent/child process structure:

As started by Python's subprocess.Popen(), both Frigcal's main GUI and PyMailGUI's per-account GUIs inherit their parents' stdout output stream, and print trace output to this stream for logging purposes as they run. This output is useful when a console is present, and is normally harmless otherwise. In Mac apps, however, it can trigger broken-pipe errors after the parent process exits, if the child process prints enough to overflow a buffer (apparently 32k, per forced-printing tests).

To compound matters, Python follows an unusual policy for broken-pipe errors: by default it translates their signals into exceptions that do not close a process, but simply cause prints to fail per normal exception handling. In GUIs, this in turn can cause entire code sections of callbacks with prints to fail, even though the GUI process lives on. This error is temporary in nature (later prints succeed again until smaller buffer overflows), and can be harmful only if it causes an important section of code to fail. In many cases, the print fails silently and safely, and the program simply continues.

Nevertheless, this broken-pipe error has been observed to cause a Frigcal Mac app's calendar backup to report an error and cancel a save, due to a failure of a backup-message print. This happened just once in nearly one year of daily usage, and was mostly bad luck: one of the backup callback's prints just happened to trigger a buffer overflow and pipe error. It was also not fatal—the GUI correctly reported the error in a popup, the backup and save worked correctly when simply rerun (as suggested in the User Guide), and no calendar data was lost. Still, this is clearly worth a work-around to avoid the potential altogether.

Scope

This issue applies to Frigcal and PyMailGUI Mac apps only. It can occur only on Mac OS X, and then only when the app version of the program is run. Both the app itself and the app's nested launcher executable may exhibit the problem, whether they are run by Finder click or Terminal command line.

On the other hand, running the source-code version on Mac either by command line or click does not trigger the error. Moreover, Linux and Windows do not have the issue in any context. On Linux, clicked executables route stdout to a log file, and command-line runs correctly route inherited stdout streams. On Windows, GUI executables drop printed text altogether, and console runs handle child process output without error in all cases.

It's also worth noting that the issue was observed only on Max OS X El Capitan (10.11). Impact on other releases of Mac OS X is unverified and unknown, and the most likely suspect behind the problem may be version-specific, per the next section.

Cause

This issue's limited Mac-app scope strongly suggests that either:

Of the two, the frequent changes and other idiosyncrasies of the Mac's app-related system code make it the prime suspect (and alas, Apple documentation for this case seems elusive or nonexistent). Though a smoking gun remains to be found, this likely reflects either a misfeature or bug in Mac app-launcher code; may not be present in other versions of Mac OS X; and seems typical of the hurdles that both Apple and Microsoft platforms present to developers on a regular basis.

Regardless of the source of the error, however, a work-around is in order.

Work-around

Luckily, the work-around for this issue is easier than its attribution. To fix, the launchers simply set the child process's stdout to os.devnull (i.e., /dev/null) when running as a Mac app only, so that all text written or printed to this stream is routed nowhere. Specifically, launchers pass stdout=subprocess.DEVNULL instead of None to subprocess.Popen(), and do the same for stderr, in this single context.

Other Mac run modes and other platforms continue to generate stdout to the inherited stream as before. The only functionality lost by the work-around is PyMailGUI account-viewer trace output for Mac apps, which formerly wound up in the Mac Console unless the launcher process was manually closed (but, curiously, only after an account child process exited altogether).

To see the code patches applied, search for function startFrigcal() in Frigcal's launcher, and similar in PyMailGUI's equivalent. Note that the architecture of PyEdit and Mergeall make them immune to this issue: PyEdit does not have a launcher process, and Mergeall runs as a GUI that manually reads a main-script process's printed output through an explicit pipe. Because neither has a child process that prints to a stdout stream connected to a system console, neither is at risk.

As a general rule, though, programming Mac apps can be perilous work. In the name of subjective user experience, they must do their business in a highly proprietary realm that seems rife with special cases, and has little to do with Unix fundamentals. As always, develop with care.




[Home page] Books Code Blog Python Author Train Find ©M.Lutz