May 11, 2011

Bilingual Memory Management

I’m a Mac Chrome software engineer. Mac software engineers spend a lot of time reading and writing Objective-C. Chrome software engineers spend a lot of time reading and writing C++. I spend a lot of time reading and writing both.

Both C++ and Objective-C are object-oriented extensions of the classic C programming language. They each take different approaches to achieving their goals at the language level. Each also has its own standard library, and its own set of common idioms used to realize certain behaviors. Although at a conceptual level the languages share similarities, they’re also very different.

Objective-C++ makes up for some of these differences. It’s a language that effectively takes all of C++ and Objective-C and puts them into a blender together. It allows C++ calls to be made from Objective-C code, and vice-versa. Objective-C++ eases the burden of having to deal with the two distinct languages in projects like Chrome that need to use both of them.

Scopers

Chrome, leaning on its C++ roots, tends to make use of C++’s memory-management features. In many cases, Chrome adheres to an object-ownership model that relies on the fact that when an object goes out of scope, it will be destroyed, cleaning up after itself along the way, releasing the memory it occupied to be used for other purposes. To assist in managing this process, Chrome even has template classes that our team informally calls “scopers.” Most widely used is scoped_ptr<>, which is similar to the C++ standard library’s std::auto_ptr<>, and even more similar to tr1::scoped_ptr<>. scoped_ptr<> maintains ownership of a pointer to a C++ object, deleting the object with the C++ delete operator when it goes out of scope.

Clean-up tasks are commonly delegated to scopers: once scoped_ptr<> holds a pointer, the program is guaranteed to delete the object when the scoped_ptr<> goes out of scope, regardless of any return statements or anything else that interferes with the program’s flow. Used in this way, scopers can eliminate cleanup points which, in traditional C, often involve the use of goto. This cleanup code often becomes cumbersome and maintaining it properly is an error-prone process as a program matures. Scopers declare “here’s something that needs to be cleaned up” exactly where the thing that needs to be cleaned up becomes your responsibility, and they automate the cleanup process. This leads to code that’s easier to read and easier to work on.

Another scoper that’s especially relevant to Objective-C++ is scoped_nsobject<>. scoped_nsobject<> works similarly to scoped_ptr<>, except it owns an Objective-C object, and will call -release on the object when it goes out of scope. Since scoped_nsobject<> can be used in the C++ portion of Objective-C code, it’s an exceptionally handy way to gain the benefits of scopers in Objective-C++ files. scoped_nsobject<> is a C++ template class dedicated to operating on Objective-C objects.

Chrome code uses scoped_nsobject<> in preference to the standard Objective-C -autorelease method in cases where it makes sense to do so. It also uses scoped_nsobject<> to maintain ownership of Objective-C objects held in instance variables of other objects, whether those other objects are written in Objective-C or C++.

In C++, using a scoper as an instance variable ensures that when the object is destroyed, the scopers it owns will also be destroyed. Because scoped_nsobject<> will -release the Objective-C object it’s responsible for, this provides a way to integrate management of Objective-C objects into C++’s way of doing things. Objective-C itself doesn’t normally offer this feature, but in Chrome, we’ve enabled the -fobjc-call-cxx-cdtors compiler option, which ensures that destructors for C++ objects (like scoped_nsobject<>) held in instance variables will be called when an Objective-C object is deallocated. Using scoped_nsobject<> like this, in conjunction with the Objective-C language extension, means that we no longer need to write -dealloc methods that simply -release all of the objects that the object owned. Instead, we stick each Objective-C object that needs to be maintained as a strong reference in another Objective-C object into its own scoped_nsobject<>. This gives Objective-C objects the ability to automatically destroy other Objective-C objects that they own, C++-style. It’s been a boon for both readability and maintainability, and has almost certainly kept us from making sloppy errors that would cause memory leaks. In fact, the only real readability problem with scoped_nsobject<> used in this way is that you might have had to go over this paragraph multiple times in order to take it all in.

The Objective-C Property Releaser

scoped_nsobject<> was great, but as Objective-C matured into Objective-C 2.0 and we began adopting some of its newer features, we hit a snag. Objective-C 2.0 introduced @property. Properties can refer to instance variables whose accessors are generated automatically by the compiler, using @synthesize. Properties, especially when coupled with synthesized accessors, can reduce the amount of code that needs to be written. Unfortunately, there’s no provision to release retained properties automatically when an object is deallocated. This seems like a major omission to me, but unfortunately, Apple never consulted me when developing Objective-C 2.0. Chrome code had been using scoped_nsojbect<> to handle this, but synthesized properties require raw pointers to Objective-C objects, and don’t work with C++ objects such as scoped_nsobject<>.

A coworker spotted the impending doom. Given the tools at our disposal, the apparent options were:

  1. Avoid using scoped_nsobject<> for properties marked retain, and @synthesize the accessors. This would put us back to having to call -release from -dealloc methods again, but we wouldn’t have to write the accessors ourselves. Remembering to write all of those -release calls is error-prone and would lead to memory leaks.
  2. Let scoped_nsobject<> handle properties marked retain, but don’t let the compiler synthesize any accessors. This would mean that for each retained property, we’d need to write our own accessors that understood how to interact with the scoped_nsobject<>, but we wouldn’t have to call -release from -dealloc. Having to write all of those accessors as boilerplate code isn’t my idea of fun.

Dissatisfied with these options, I cooked up a solution more in line with my idea of fun. I came up with base::mac::ObjCPropertyReleaser. It’s another example of using C++ features to make Objective-C better. It brings the language closer to where I think it should be, and almost makes up for Apple forgetting to ask for my feedback when they were designing Objective-C 2.0.

An ObjCPropertyReleaser is a C++ object that can go directly into any Objective-C object as an instance variable. In the -init method (or other appropriate designated initializer), the property releaser is told which Objective-C object owns it, and which class that object belongs to. When the Objective-C object is deallocated, the -fobjc-call-cxx-cdtors compiler option causes the ObjCPropertyReleaser’s destructor to run. Taking advantage of the Objective-C runtime’s support for object introspection, the property releaser then determines which of the object’s declared properties are marked retain or copy, finds the instance variables backing those properties which are synthesized, and sends them a -release message.

To sum it up more succinctly, ObjCPropertyReleaser releases everything backing a synthesized property marked retain or copy, and it does it automatically when the object is deallocated.

The property releaser saves us from having to call -release manually as needed from -dealloc methods, and saves us from having to write boilerplate accessors because of the incompatibility between @synthesize and scoped_nsobject<>. It takes the best aspects of scoped_nsobject<> and @synthesize without any of the drawbacks they have relative to each other. It also means that we get to spend less time writing code, freeing us up to spend more time doing other things, like writing columns.

Experienced Objective-C developers might wonder why the property releaser needs to be initialized with both the object that owns it and that object’s class type. After all, you can always determine an object’s class by calling its -class method. As it happens, this would be an incredibly dangerous thing to do when subclassing comes into play. -class always returns an object’s most-specific type, which might not be the type that a given instance of the property releaser is supposed to be responsible for. I considered other ways to design the property releaser, such as making it a base class that could reach into all of its subclasses’ properties, but this seemed like a bad idea. I don’t believe that a base class should ever screw around with subclass’ data. The base class idea would have also made it difficult to use the property releaser in a class that needed to extend another base class but didn’t use the property releaser itself.

In case you’re not using -fobjc-call-cxx-cdtors, you can still use ObjCPropertyReleaser. Just call its ReleaseProperties method from your -dealloc method. Be sure not to do both, though: don’t call ReleaseProperties directly in conjunction with -fobjc-call-cxx-cdtors.

A Gift for You

Both scoped_nsobject<> and ObjCPropertyReleaser are almost entirely self-contained and you might find that they’re completely at home in your project, even if your project isn’t Chrome. The property releaser even comes with a Sesame Street-inspired unit test, which is my current favorite bit of Chrome code. Here are the raw files from Chrome’s Subversion repository:

Four! Four files! Ah, ah, ah.

A Gift for Me

Today’s my birthday. You probably forgot to get me something. That’s fine: if you like the scoper or the property releaser and choose to share them with your favorite Mac developer, that’s enough of a gift for me. If not, well, there’s always next year.

May 3, 2011

A Touch of Yellow

Yesterday, Google announced the availability of Google Chrome Canary for Mac. The Canary is a version of Chrome that’s updated very frequently—in most cases, daily. It offers the absolute latest version of Chrome, closest to what most developers are working on. It’s the first place to see new features, bug fixes, and other changes to Chrome. It’s also entirely untested, so it might not even launch, and if it does launch, it might work so badly that you’d wish it hadn’t.

This version of Chrome has been available for Windows since last August, and it joins the dev, beta, and stable Chrome channels on the Mac. These channels allow users to choose whether to run a very well-tested and well-supported but older version of Chrome, labeled the stable channel, a more recent (and fun) version like the Canary, or something in between. Dev (short for developer) channel releases usually come weekly and are minimally tested so that users are spared major brokenness. The beta channel is tested more thoroughly and is used to stabilize releases before they’re promoted to the stable channel.

Early Warning

The Canary is actually an important part of Chrome’s strategy in that it enables the six-week “release early, release often” schedule to work as well as it does. By getting a Chrome update out to Canary users daily, the amount of time we wait for feedback is dramatically reduced. If a new feature causes Chrome to crash frequently, we’ll know about it within a day of turning that feature on. If an engineer fixes that crash, we’ll have validation that the fix works within a day of making the repair.

The rapid feedback provided by Canary users was the inspiration for its name. The Chrome Canary is our equivalent of a canary in a coal mine, which would show signs of oxygen deprivation or gas poisoning as an early warning to workers that a hazardous condition existed. When a hazardous condition exists in Chrome, the Canary will warn us about it long before we’ll find out from dev, beta, or stable channel users.

Like the other Chrome channels, Canary feedback comes in two ways: from bug reports entered by users, and from usage statistics and crash reports that Chrome provides automatically. One difference between the Canary and the other channels is that in the Canary, the checkbox to enable these automatic reporting features is on by default. (The option is always presented at installation time.)

Silvery Metallic

In a sense, the Canary builds are similar to the existing Chromium snapshots that some users are running. Chromium snapshots are produced automatically, approximately hourly, and are also entirely untested. Chromium snapshots don’t include any of the automatic reporting that the Canary does, so we were missing an important feedback channel when users chose to run Chromium instead of a version of Chrome. The Chromium snapshots also don’t include any automatic updater, which has prompted some to devise their own mechanisms to keep up-to-date. There are other differences between Chromium and Google Chrome, but the Canary intends to remain true to Google Chrome as closely as possible.

Monochromacy

There are a few areas where the Canary intentionally differs from Chrome’s existing dev, beta, and stable builds. As mentioned above, automatic usage statistics and crash reporting are enabled by default in the Canary, emphasizing its role as part of a feedback-gathering system. The Canary can also be installed alongside any other version of Chrome, providing a sort of escape hatch: if the Canary ever winds up unusable, just use a non-Canary version of Chrome for a few days. In fact, the Canary uses a completely different set of settings than any other Chrome installation, so you can run both of them simultaneously, and they won’t interfere with one another.

The Canary can’t be set as your default browser. That’s the official line, anyway, and the features in Chrome that allow it to offer to set itself as the default browser have been disabled in the Canary. Practically, all this means is that when you click on a web link in another application, it’ll open in some other browser, not the Canary. I’ll let you in on a little secret, though: if you’ve installed the Canary on your Mac and really want to use it as your default browser, you can set it as the default web browser in Safari’s preferences. The irony of invoking another web browser to make this work isn’t lost on me, but sometimes, you’ve just got to swallow your pride for a moment to get what you want.

How It’s Done

At the nittiest and grittiest level, producing the Canary is just a minor twist on Chrome’s established build process. When we build Google Chrome, it doesn’t have any idea what sort of channel it’s going to be released to, or whether it’s going to be a Canary. It just knows it’s Google Chrome. The specialization happens at the very end of the process. A script takes these “undifferentiated” but complete builds of Google Chrome, makes a few copies of them, and then makes the necessary changes in the copies to turn them into dev channel builds or Canaries or whatever else is called for.

Many of these changes occur in the browser application’s Info.plist. In the case of the Canary, specialization means that the automatic updater will be configured to treat the Canary as a distinct product from Google Chrome so that the two can coexist side-by-side. This is done by setting KSProductID to com.google.Chrome.canary instead of com.google.Chrome. A similar change is made to CFBundleIdentifier so that Mac OS X doesn’t get confused between Google Chrome and the Canary. The Canary has a setting named CrProductDirName, which is set to Google/Chrome Canary, and the auto-updater is set to use the canary channel by setting KSChannelID to canary. The colorful application and document icons are replaced with yellow ones, the managed preferences manifest is tweaked and renamed corresponding to the other changes that were made, and that’s it. Ding! It’s done, and ready to be released to users without any further delay.

Notably, there’s absolutely no difference in code anywhere between the Canary and other channels of Google Chrome. The side-by-side feature is enabled by basing the location to store settings on CrProductDirName. In every other way that the Canary needs to vary from the other channels, it’s handled by looking at KSChannelID.

Since there’s no difference between Chrome and its Canary, if you ever wound up with a dev channel build of Chrome whose version number is identical to a Canary’s, you could compare their versioned directories and you’d find that they’re 100% identical. If you’re interested in trying this experiment, the stars should align properly for you once a week.

Running Simultaneously

If you want to run Chrome and the Canary side-by-side, you might benefit from these tips. Having the Canary operate out of an entirely different group of settings than other Chrome builds is part of what enables the two to run simultaneously, but it can also be frustrating if you’ve amassed a large collection of bookmarks, extensions, or other settings. If you enable Sync in each, they’ll share data, while maintaining their distinct presences on your hard drive.

If you find that you’re running both Chrome and the Canary simultaneously and need a way to distinguish them, consider giving each a different theme to provide some visual distinction. Personally, I’d go with something heavy on the yellows for the Canary.

How Do I Choose?

Chrome Stable, Chrome Beta, Chrome Dev, Canary, and Chromium. Still not sure which to choose, even in spite of my warnings of gas poisoning?

Handle this like you’d handle the purchase of a new car. Just pick by color.