GPSTrack 3.0.2 Released

August 14th, 2025

Today, I released an update to GPSTrack, my privacy-focused GPS tracking app. It is available on the App Store.

  • New Features
    • In Track Details, added chart grid lines for minimum/maximum elevation and speed.
    • Restore support for sharing tracks via email.
      • This feature is available with a GPSTrack Plus subscription, and for users who purchased the app before GPSTrack 3.0 was released.
  • Improvements
    • Allow swipe to unarchive for archived tags
    • Performance improvements
    • Improvements and bug fixes for upcoming features
  • Bug Fixes
    • When adding a track to the map, if the track was the most recently recorded track and still on the map (blue), its color wouldn’t be changed (to red).
    • Fixes the current track not drawing in some cases.
    • Improve handling of upgrading v2.x databases with no saved tracks.
  • Other
    • This is the last version supporting iOS 17. Future versions will require iOS 18.

GPSTrack 3.0 Released

June 10th, 2025

Today, I released GPSTrack 3.0, my privacy-focused GPS tracking app for iOS. It is available on the App Store. This is the first public release since 2014.

GPSTrack 3 is a complete rewrite from the last released version, now in Swift and (mostly) SwiftUI. The changelog is so long it’s almost better to think of this as a completely new app.

This version switches from being paid-up-front to a subscription model. While paid-up-front worked in the early era of the app store, it’s no longer a sustainable approach for most developers, and as much as I dislike subscriptions myself, switching to subscriptions is the only way I can justify further development.

Users who purchased GPSTrack while it was previously on sale have full access to all functionality that was available in the last public release (2.2.1). Some (but not all) new features require a subscription. If you previously purchased GPSTrack and are not sure if the app is recognizing your prior purchase, please feel free to reach out.

SwiftUI Memory Leak Workaround

October 12th, 2023

The new Observation framework in iOS 17 (and aligned macOS, tvOS, and watchOS releases), with its @Observable macro, provides a fantastic and higher-performance way for SwiftUI applications to trigger view updates, compared to the older ObservableObject approach.

To do this, the framework retains strong references to reference objects that are being tracked. However, this combines with a bug in SwiftUI that causes objects in views to not always be released when the view is dismissed.

[Update: The bug in SwiftUI was fixed sometime after iOS 17.1, so this tool is no longer needed. It is available for historical insight.]

The Problem

This manifests when a SwiftUI view presents a sheet or fullScreenCover and the view captures an object. The object is retained by the system, but is not released when the presented view goes away.

For small objects, this may not be a problem, unless they’re also tracking notifications or other external events.

Larger objects are the main problem; I discovered this issue when I noticed one of my apps using several gigabytes of memory when it had no open windows.

This bug is present in at least iOS 17.0 .. 17.1b3.

To solve this problem, I created the SwiftUIMemoryLeakWorkaround package. This package provides a way to resolve the problem in a way that should be relatively backwards-compatible when the OS bug is eventually fixed.

The Solution

The solution is to instead have a UIViewController handle the presentation of sheets. This is accomplished by injecting a coordinator object into the SwiftUI environment that has a UIViewController that is the parent of the SwiftUI view. Then, the SwiftUI view is modified to call the coordinator, rather than sheet or fullScreenCover directly. The coordinator uses its stored view controller to present a new view, and creates a new coordinator to inject into the sheet view with the child view controller.

An extension on View provides accessors (leak_workaround_sheet and leak_workaround_fullScreenCover) that create a view modifier that uses the coordinator to trigger presentation. In the event the coordinator is not set or is set to nil, it falls back to the system behavior.

The included Example.xcodeproj demonstrates both the problem and the solution.

This is not a perfect solution. Sometimes, the coordinator itself is leaked. The coordinator object contains two weak references and an optional UUID, and so is relatively tiny compared to the view models that would likely be leaked instead.

Announcement: php[tek] 2017 Conference Talks

January 12th, 2017

I’m pleased to announce that in May, I will be giving two talks at the excellent php[tek] conference in Atlanta, GA. One will be a technical talk on computational algorithmic complexity. The other is a comparison of long-distance hiking and software development, which I developed over the course of my Appalachian Trail thru-hike, and which I’m particularly looking forward to giving.

I missed last year’s php[tek] because I was on the Appalachian Trail at the time. This was the first tek I’ve missed since 2010, so I’m happy that I get to go this year partially to talk about why I wasn’t there last year!

For more information on my Appalachian Trail thru-hike, please feel free to see my hiking blog, longstride.net.

PHP Misleading Error: Maximum execution time of 0 seconds exceeded

December 2nd, 2016

Yesterday, on freenode #phpc, someone posted this curious error message:

PHP Fatal error: Maximum execution time of 0 seconds exceeded

Jokes aside of how, only eight hours into December, they had exceeded their monthly allotment of PHP time, this is a rather curious error message to receive: PHP’s set_time_limit() function and associated max_execution_time ini setting define a limit of 0 to mean no limit. A script with no limit should not be hitting a maximum execution time of zero seconds!

While several of us tried brainstorming possible causes, additional details were provided, including that the script, running on PHP 7 on unix, under php-fpm, was twice terminated after running for roughly two hours.

I set out to look at PHP’s source code, certain that there would be something that would shed light on this mystery.

Let’s investigate PHP internals

The first thing I did was search for “Maximum execution time” in the PHP sources, hoping to find exactly one hit. Ignoring test cases, that string appears exactly once in the source, in the zend_timeout() function in zend_execute_API.c.

When zend_timeout() is called, PHP calls a function specified by the SAPI (the interface between PHP and the web server) if one is defined, and then emits an error and exits.

Knowing now where the error message is generated (and that it only comes from one place), I needed to determine what calls zend_timeout(), so I searched the code for that function’s name. Excluding the function’s own definition, and its declaration in a header file, there were four results. Two of those results were specific to Windows support, and could be ignored.

The other two results were conveniently located next to each other in zend_set_timeout(), both used as the callback function to a signal handler.

This means that, in normal use, the execution time limit message can only be generated in response to receiving a signal.

Signals

In UNIX-like systems, signals provide for a limited form of inter-process communication, in the form of sending and receiving an interrupt with a numeric identifier. The Wikipedia page on signals provides additional detail.

There C standard defines six signals, but many operating systems define and use many additional signals.

Some signals sent to a process cause its unconditional termination (e.g. SIGKILL) or have other operating-system level effects (such as SIGSTOP and SIGCONT). Most other signals can be caught by the target process if it has installed a signal handler to receive that notification. (If a signal handler is not installed, a default handler is used, which often, but not always, results in the self-termination of the process.)

How PHP’s execution time limit works

So, then, what signal triggers zend_timeout()? A few lines up indicates the answer: SIGPROF. (Alternatively, SIGALRM is used when running under Cygwin on Windows.)

SIGPROF was a new one to me. Wikipedia says:

The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the time limit specified in a call to a preceding alarm setting function (such as setitimer) elapses. SIGALRM is sent when real or clock time elapses. SIGVTALRM is sent when CPU time used by the process elapses. SIGPROF is sent when CPU time used by the process and by the system on behalf of the process elapses.

And indeed, there is a call to setitimer() before PHP installs the signal handler on SIGPROF. The man page for setitimer() describes how to use it to set a timer that counts time the process spends executing, triggering a SIGPROF after the timer elapses.

Some more searching through PHP’s code makes clear how the entire process works: when PHP is loaded, set_time_limit() is called, or max_execution_time is changed, PHP clears the timer (if set), and re-sets the timer (if the timeout is non-zero). Additionally, during request start-up, the signal handler on SIGPROF is installed (regardless of whether a timeout is actually set).

When the timer set by setitimer fires, the SIGPROF handler, zend_timeout() runs, displays the error message with the number of seconds filled in from the current configuration, and exits.

Reading between the lines

That answers the question of how the execution time limit error was displayed. But it doesn’t answer why: one would naturally assume that since the timer isn’t set, the operating system won’t send SIGPROF, and the script wouldn’t be terminated.

The answer lies in one further detail of the UNIX signal mechanism: any process can send any other process any signal (except where disallowed by security policy, such as for processes owned by a different user).

This means that a script doesn’t actually need to hit the time limit to be killed. PHP just needs to be told that it has.

You can test this yourself by doing something similar to this:

$ php -r 'posix_kill(getmypid(), SIGPROF);'

Fatal error: Maximum execution time of 0 seconds exceeded in Command line code on line 1

So, what really happened?

Unfortunately, a signal does not come with the identity of its source. Were that the case, it would have been easy to determine what was killing the process and what configuration to change to stop it. In this case, the resolution was to modify the php code to “only” take 20 minutes to run, so the timeout was no longer an issue.

If we make the assumption that there wasn’t a malicious user on the server sending unwanted signals as a denial of service attack, the documentation for max_execution_time hints at one possibility:

Your web server can have other timeout configurations that may also interrupt PHP execution. Apache has a Timeout directive and IIS has a CGI timeout function. Both default to 300 seconds. See your web server documentation for specific details.

With some further research, I found that neither Apache nor nginx explicitly send SIGPROF, though. And while nginx does use setitimer, its use triggers a SIGALRM.

My best guess is that there likely was some sort of watchdog process on the server that killed off the process running php after it consumed too many resources (either memory or time).

It's probably a bug (or at least, undesirable confusion) in PHP that a SIGPROF that doesn’t arise strictly from a timer expiration displays the same message as if the timer did expire, but this looks to be correctable.

20 Years of PHP

June 8th, 2015

Today is PHP’s 20th birthday. Ben Ramsey has called on us to blog about our history with PHP, so here’s mine.

Way back in 1999, still in college, I got my first real software development job at a small company in DC, the predecessor to my current employer. My job was to write an Apache log analyzer, because the software package we were using at the time was very slow and produced inconsistent results between runs.

So I wrote it in C++, because that’s what I knew, and what I was using for my personal projects. But, we were a web services company, so why shouldn’t our log analyzer be accessible via the web?

We were using a couple of different web languages at the time. Some of our early stuff was in PERL, which I had tried before and didn’t like. We also had a site using this awful language called SQLWEB. But, it was suggested to me that I write the web interface using this scripting language called PHP. I had never heard of it before, but I quickly learned it (because, frankly with PHP 3, there wasn’t much to learn), and quickly became enamored with this language.

Sure, it didn’t have many of the features we’ve come to take for granted in modern PHP, such as OOP or closures, or even the foreach keyword (hooray for PHP 4!). But its key feature was that it didn’t need to be compiled. Up until then, every program I’d ever written had a slow write-compile-debug cycle, because compiling a new build and relaunching the app to test was always slow. But here, with PHP, all I needed to do was change my code and refresh the browser window, and the changes were immediately visible. PHP may have been slower than C, but I was way more productive.

We no longer use that log analyzer, but PHP is the foundation for every website we currently manage, and is the vast majority of the code I’ve written over my professional career. And since then, the PHP community has become so much bigger, with several different application frameworks, thousands of open source libraries made easily available through Composer and Packagist, more conferences every year than one person could possibly attend, and a great community that I’m happy to be a part of.

Happy birthday, PHP! Here’s to another 20 years of powering the web.

BowerBundle Released

November 24th, 2014

Last night, I released BowerBundle, a super-simple bundle for Symfony that enables running Bower update/install automatically after running Composer update/install. I’ve used this for several different Symfony apps, and now it’s available for your use as well.

You can download it via GitHub, or via jbafford/bower-bundle in Composer.

Slides for “Writing OOP Modules for Drupal 7” Posted

November 19th, 2014

The slides for “Writing OOP Modules for Drupal 7”, my first talk at last week’s php[world], have been posted to Speaker Deck.

Slides for “Stupid PHP Tricks” Posted

November 18th, 2014

The slides for “Stupid PHP Tricks”, my second talk at last week’s php[world], have been posted to Speaker Deck.

Automatic Build Versioning in Xcode with Subversion

November 17th, 2010

For awhile, I had wanted to include the svn revision number in my iOS app, and when I came across a blog post by Daniel Jalkut from a few years ago, I thought I had found an answer. No sooner than I implemented his script, though, did I discover that Apple doesn’t allow build numbers in iOS app packages.

I still really wanted this information in my dev builds so it would be easier to keep track of what version I was working with, so wound up I made a number of modifications to the script to make it suit my needs:
Read the rest of this entry »