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.
By design, Frigcal and PyMailGUI both use a standard parent/child process structure:
PyMailGUI's per-account GUIs are also started by a launcher parent process, which allows users to open multiple independent email account viewers as child processes. Its launcher normally endures to handle future opens, but it can be closed manually by a user.
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.
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.
This issue's limited Mac-app scope strongly suggests that either:
stdout
streams inherited by child processes
spawned by an app's main process. Per the scant (and rarely authoritative)
information on the web, this appears most-likely related to the Mac's
launchd
program-launcher daemon, and/or its
syslogd
console handler.
The bootstrap (start-up) code provided by the py2app app-bundle builder is somehow breaking this child-process use case. It's difficult to imagine how py2app's code could botch later subprocess spawns, but its magic is not always benign.
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.
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.