Going "Serverless"

When my friend Nelson and I set out to build our location tracking app & accompanying website, Wanderings, we started down the familiar path of choosing our stack. Flask seemed like a reasonable choice. Postgres for the database. SqlAlchemy for the ORM? Sure, I guess. We figured we’d try hosting on GCP, mostly to learn more about the platform. Do we bother with containers? Meh, leave those for the youngsters. And so on.

But the more we thought about hosting our own service, the less enthused we were. The GCP costs were piling up, and we didn’t really want to be responsible for having a copy of our users’ location data. One of the driving ideas behind Wanderings was having a location tracker where you controlled your own data. Why should you trust us with a copy of it?

I should back up and talk a little about what Wanderings is and how it works.

Back in 2011 there was a small uproar when it was revealed that Apple was apparently tracking your every move with your iPhone. Out of that grew a project, OpenPaths, that allowed you to generate your own location data, and have full control over it. The app used very little energy, so you could just start it and forget it.

However, with the release of iOS 11, OpenPaths stopped working, and its accompanying website, openpaths.cc, no longer resolved. It appeared to be no longer maintained. There were some other location tracking apps out there, including Google Timeline, Fog of World, and Strut, but none that had the combination of strong privacy guarantees , simplicity, and low-energy usage that we were looking for. So we decided to make our own.

The Wanderings iPhone app is pretty basic. It uses iOS’ significant location change service to only update your location when you move by a significant amount. It also relies on wifi and cellular for location information rather than power-hungry GPS. It does all this in the background, so you can start it once and forget it.

New location data is uploaded to your private, secured CloudKit database on iCloud, so you are the only one with access to your data.

So back to our server conundrum.

Wanderings Screenshot One of our requirements was a website that would render the full heat map of your travels, let you export that data, and eventually let you do a lot of interesting things with your data. Of course, it doesn’t make sense to re-download all your location data every time you visit (especially since CloudKit only lets you pull 200 records per request) so we needed some kind of cache of your data in the app, as in a database.

But we don’t really want your data. And we really don’t want to deal with maintaining a server somewhere, either.

Fortunately, for a few years now browsers have supported IndexedDB, an embedded transactional database with sizable storage limits. We realized if we used this as our cache, the app could be a static site (something Nelson had explored years ago), which we can host cheaply anywhere. In fact, we ended up just using GitHub Pages to host the site right out of our repository.

Using a browser-embedded database has the downside of having to re-download all your data when you go to a new browser or if you’ve cleared your cache, but we think that’s an acceptable trade-off to have complete control of your data.

So check it out and let us know what you think. Send your bugs/feature requests/love to [email protected].

Code Signing Will Kill Me Yet

This is one of those I-spent-long-enough-stumped-on-this-issue-I-should-write-it-up-for-future-generations posts.

I wrote a little app at work (which I’ll talk about in a future post), and I was having a strange code signing issue. The app would sign just fine:

$ spctl --assess -v Orwell.app
Orwell.app: accepted
source=Developer ID

But then if I zipped it up and sent it to someone, when they unzipped it, OS X would tell them that it was damaged and should be thrown away:

Blech

Sure enough, if I zip and unzip the app, and verify the code signing again, I get:

$ spctl --assess -v Orwell.app
Orwell.app: a sealed resource is missing or invalid

After much hair-pulling, I found Technical Note TN2318: Troubleshooting Failed Signature Verification, which says:

The file prefixed with “._” is problematic and a was the result of copying certain Mac OS X files to a non-HFS+ formatted disk. These files are referred to as Dot files, Apple Double files, or resource forks. They are invisible to Finder but can be removed using the dot_clean utility.

Sure enough:

$ find Orwell.app -name "._*"
Orwell.app/Contents/Resources/app/node_modules/applescript/._package.json
Orwell.app/Contents/Resources/app/node_modules/applescript/lib/._applescript.js
Orwell.app/Contents/Resources/app/node_modules/applescript/samples/._execString.js
$ dot_clean Orwell.app
$ find Orwell.app -name "._*"

Running dot_clean before I signed the app fixed the issue.

(Orwell is an Electron app, hence the node_modules dir, in case you were wondering.)

Fixing A Swift Memory Leak

I wrote a Mac menu bar app for internal use at Etsy that show the status of a number of internal systems and pops up a notification if any of them change status—for example, if a problem is detected and someone puts a hold on our deployment queue. EtsyInternalStatus.app

Some months ago, a colleague noticed the app was leaking memory. I put off looking at it until recently, when I noticed that my own instance was eating up 3+ GB of memory.

Fortunately, Apple provides some great tools for tracking down leaks. One is Instruments, an incredibly powerful tool that I know thiiis (puts thumb and forefinger a millimeter apart) much about. But I know enough to inspect a process for leaks.

I started Instruments, selected the Leaks profiling template, and attached it to my leaky app, and saw this:

Holy leak, Batman!

Yup, that’s a leak. You can also drill down to see where the leak is coming from:

Drilling down to the leak

So the leak is obviously in the networking code, seemingly in Apple’s part of it.

I should pause here to say a bit about how the app works. It’s pretty simple: every 3 seconds, it fetches an internal status web page, parses out the statuses from an HTML table (writing a JSON endpoint for the status page is on my todo list!), and updates the menu accordingly.

The networking code in the app was pretty simple:

let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { data, response, error in
// check for error, do something with the data
}
task.resume()

NSURLSession was introduced with in Mavericks and iOS 7 as a replacement for NSURLConnection.

I tried setting the cache sizes to 0, and invalidating the session when I was done, as recommended in this article, which seems to describe the same leak:

// THIS CODE DID NOT HELP!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.URLCache = NSURLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
let session = NSURLSession(configuration: configuration)
defer {
session.finishTasksAndInvalidate()
}
let task = session.dataTaskWithURL(url) { data, response, error in
// ...

But this didn’t help. What did end up working was using an ephemeral session:

let session = NSURLSession(configuration: NSURLSessionConfiguration.ephemeralSessionConfiguration())
let task = session.dataTaskWithURL(url) { data, response, error in
// ...

With that in place, here’s what Instruments showed me:

No leaks!

Yay! No leaks!

We Don’t Need No Stinkin’ GUI

If you’re more of a command-line gal, there’s another way to investigate leaks built into OS X: the leaks command:

$ leaks EtsyInternalStatus
Process:         EtsyInternalStatus [24147]
Path:            /Applications/EtsyInternalStatus.app/Contents/MacOS/EtsyInternalStatus
Load Address:    0x105eaf000
Identifier:      com.etsy.EtsyInternalStatus
Version:         1.2.3 (1.2.3)
Code Type:       X86-64
Parent Process:  ??? [1]

Date/Time:       2015-09-02 20:33:26.173 -0700
Launch Time:     2015-09-02 20:30:44.447 -0700
OS Version:      Mac OS X 10.10.5 (14F27)
Report Version:  7
Analysis Tool:   /Applications/Xcode-beta.app/Contents/Developer/usr/bin/leaks
Analysis Tool Version:  Xcode 7.0 (7A192o)
----

leaks Report Version:  2.0
Process 24147: 25225 nodes malloced for 15639 KB
Process 24147: 615 leaks for 5321984 total leaked bytes.
Leak: 0x10a02f000  size=135168  zone: MallocHelperZone_0x105f65000  length: 5020
...
<snipped 7800 lines of leaked memory dumps>

Now, the fixed version:

$ leaks EtsyInternalStatus
Process:         EtsyInternalStatus [85066]
Path:            /Applications/EtsyInternalStatus.app/Contents/MacOS/EtsyInternalStatus
Load Address:    0x102d2f000
Identifier:      com.etsy.EtsyInternalStatus
Version:         1.2.4 (1.2.4)
Code Type:       X86-64
Parent Process:  ??? [1]

Date/Time:       2015-10-10 10:27:29.556 -0700
Launch Time:     2015-10-10 10:16:11.476 -0700
OS Version:      Mac OS X 10.10.5 (14F27)
Report Version:  7
Analysis Tool:   /Applications/Xcode.app/Contents/Developer/usr/bin/leaks
Analysis Tool Version:  Xcode 7.0.1 (7A1001)
----

leaks Report Version:  2.0
Process 85066: 62741 nodes malloced for 6598 KB
Process 85066: 0 leaks for 0 total leaked bytes.

The eagled-eyed among you might have noticed that about a month elapsed between me running those checks (life intervened), and there was an Xcode release in the meantime. Turns out that when I ran the leak check again on the leaky version of the app, it was fine! I’m guessing that it was a bug in Apple’s networking code after all, and it got fixed in the latest release.

Try out leaks on some of the other apps you have running. You’ll find that leak-free apps are oddly rare, and some are downright eggregious (my beloved Sublime Text text editor, in which I’m writing this post, shows “577841 leaks for 44554352 total leaked bytes.” Oof).

For more information, see Apple’s documentation on Finding Memory Leaks.

On "Working" Outside of Work Hours

Etsy’s culture puts a very strong emphasis on work-life balance. As someone who has mostly worked for startups, this took some adjusting to. The fact that Etsy IRC and email pretty much goes silent outside of normal business hours struck me as odd initially. But, I really appreciate it. Startups are stressful, and they left me feeling that there was always something work-related that I should be doing whenever I had some “free time.”

However, I love my work. I love the people I work with and the projects I work on. I love solving problems. I love coding. I wish I had more time to spend doing it. Having a family puts a massive dent in your free time. Don’t get me wrong, I love my family and am [usually] happy to spend any available time with them, but I so miss having large blocks of time to myself to just get into the zone.

So when I or other colleagues mention coding (or even checking email) after hours, or on vacation, we’re often castigated for doing so. I love that my colleagues are looking out for my well-being, but hey, we’re all individuals. If you have no desire to be anywhere near a computer outside of work hours, that’s awesome. It’s pretty cool to work at a company where that’s not only possible, but encouraged, isn’t it? But please don’t judge me for thinking that my ideal vacation would be go somewhere alone, with my laptop and a fast internet connection, and just code.1

If you’ll excuse me, I’ve got to get to bed. I like to wake up crazy early so I have a couple hours of alone time with my laptop.

  1. There is an argument that a senior engineer working after-hours sets a bad example for more junior engineers, making them feel like they should be doing the same. This is certainly something to be cognizant of, but so long as the prevailing culture makes it clear that this is in no way expected behavior, it should not be an issue. 

Two Tools for Packaging Mac Apps

I ran across two apps yesterday that make packaging Mac apps for distribution a breeze:

To easily build a professional DMG-based installer, look no further than DMG Canvas. There’s a free version with some limitations, but honestly $15 is a bargain for such a useful tool.

For more complex installs, you might need to create a .pkg file, in which case grab the free (and ungoogleable) Packages app by Stéphane Sudre.