Failures related to the WordPress APC Object Cache plugin

On a site for a newer X-Team client, APC was previously chosen as the object cache plugin. This has caused some headaches due to APC not being available while in CLI mode, rendering WP-CLI mostly broken: any commands which change parts of the database which get object-cached won’t appear until the cache gets invalidated, which may not happen anytime soon. For example, plugin (de)activation is broken in WP-CLI with APC Object Cache, as the manifest of activated plugins is cached.

Today I encountered a more serious problem with APC Object Cache.

I was making some widget changes, and I saw that my changes weren’t being reflected on the frontend. Upon reloading the widgets admin page, I also noticed that my changes didn’t seem to get saved. But if I ran wp option get widget_text, I could see my changes had been written to the database (as, of course, WP-CLI here was bypassing the APC object cache). So for some reason, APC was refusing to invalidate widget caches. Doing some more digging, I was shocked to see that the the Text widgets used in the sidebar, had multi-widget instance numbers surpassing 560,000,000. That’s right, 560 million.  The output of wp option get widget_text included:

array (
  /*...*/
  526001242 =>
  array (
    'title' => 'Lorem Ipsum',
    'text' => 'Hello World',
    'filter' => false,
  ),
  '_multiwidget' => 1,
)

Some plugin must have at some time bumped that widget instance number up, because there’s no way there have been millions of Text widgets used on the site. Only a couple dozen Text widget instances are currently present.

In any case, this incredibly-sparse PHP array seemed like a good candidate for what was causing APC cache to fail. I swapped out APC for Memcached Object Cache, and the problem went away. While the Memcached plugin stores arrays as serialized strings, just as they get stored in the options table, the APC plugin is apc_store’ing the array wrapped in an ArrayObject instance:

if ( is_array( $data ) )
    $store_data = new ArrayObject( $data );

While I couldn’t reproduce this problem on VVV or on the site’s staging environment, for some reason on production this array object failed to get stored by APC.

I’ve never had any issues with Memcached Object Cache, so I’m glad I made the switch. Not only did it fix this strange widgets issue, but it’s great to be able to once again rely on WP-CLI!

Easily turn any webcam image into video on an OS X Dashboard widget

I have a few webcams on my network which I can access via URLs like http://198.162.1.30:8080/cam.jpg. Here’s a handy little bookmarklet (pagelet?) which allows you to turn any such webcam URL into live video (here, 1 fps):

data:text/html,<img src="http://198.162.1.30:8080/shot.jpg" onload="var img = this; setTimeout( function(){ img.src = img.src; }, 1000 )">

Paste this  data:  URL into Safari and then turn it into a Dashboard widget via File > Open in Dashboard. Now you have a video Dashboard widget for your webcam! (Of course you could create a standalone HTML page and load it instead, but data: URLs are cooler and easier!)

WARNING: This could get interpreted as a Denial of Service (DOS) attack if you point this at a URL on a public web server. So it’s best to only do this for cameras on your local network.

Of course, if your camera supports MJPEG aka server-push (multipart/x-mixed-replace), you should just reference that MJPEG URL directly without any such code above.

Regularly cleaning up /tmp on a DreamHost VPS

I ran into a problem recently where I was no longer able to upload a file via PHP. I checked the server error log, and I saw entries like:

ModSecurity: Input filter: Failed writing X bytes to temporary file

Looking at my /tmp directory it contained 128MB of data. Apparently ModSecurity tries to prevent the filesystem from being maliciously filled up.

If you try to manually clean up just with a rm -rf /tmp/*, it will fail because the files are owned by dhapache and are not writable by anyone else. But, with DreamHost VPS, you have the ability to add admin users (aka sudoers). As an admin user, you can then set up a cron to automatically clear out the /tmp directory of old files:

su myadminuser
sudo crontab -e

Then in the editor which opens, add this line:

0 0 * * * find /tmp -mtime +5 -exec rm -rf {} \;

This will delete all files under /tmp which haven’t been modified in 5 days; it will happen every day at midnight.

Re: Sharing Sidebars Across a Multisite Network

In a post yesterday at WebDevStudios about “Sharing Sidebars Across a Multisite Network”, I was reminded of a workaround that I had to put into the Widget Customizer plugin. There is this unfortunate condition inside of the core wp_get_sidebars_widgets() function:

// If loading from front page, consult $_wp_sidebars_widgets rather than options
// to see if wp_convert_widget_settings() has made manipulations in memory.
if ( !is_admin() ) {
    if ( empty($_wp_sidebars_widgets) )
        $_wp_sidebars_widgets = get_option('sidebars_widgets', array());
    $sidebars_widgets = $_wp_sidebars_widgets;
} else {
    $sidebars_widgets = get_option('sidebars_widgets', array());
}

So you can see here that if wp_get_sidebars_widgets() gets called early (and it does) then the initial value returned will persist even if the underlying sidebars_widgets option changes (or is filtered); after this function is called on the site frontend, the returned value will persist in that private global variable $_wp_sidebars_widgets.

This was a problem for Widget Customizer because it needed to be able to override the sidebars’ widgets on the fly within the customizer preview. Fortunately, the return value of wp_get_sidebars_widgets() is filterable via the sidebars_widgets hook, and so this is what we did to get around the above behavior:

add_filter( 'sidebars_widgets', function ( $sidebars_widgets ) {
    $sidebars_widgets = get_option( 'sidebars_widgets' );
    unset( $sidebars_widgets['array_version'] );
    return $sidebars_widgets;
} );

Would this workaround also allow switch_to_blog() to be able to render sidebars from other blogs on the network?