On the usefulness of unit testing

2008-03-04 19:00:00 -0500


I’m not sure how I came across this article, I think maybe it was linked off the Google Reader blog, but Cedric has some interesting things to say about Test-Driven Development. I would just like to take issue with one small assertion of his:

Keep in mind that functional tests are the only tests that really matter to your users. Unit tests are just a convenience for you, the developer. A luxury. If you have time to write unit tests, great: they will save you time down the road when you need to track bugs. But if you don’t, make sure that your functional tests cover what your users expect from your product."

I can tell you from experience that having poor unit testing can make your functional testing a total nightmare because, in a sense, you may have good functional code working with bad “data”, in this case the unit code. Garbage in, garbage out, as was hammered into my head back in school. You can end up wasting more time, in this regard, and unit testing is no longer a thing of luxury, but something that could have saved you a few hours of head scratching.


PingMe - New Support for Pester-Repeat Pings

2008-02-21 19:00:00 -0500


Suppose you have something really important to do every week, like checking that your car is still legal for street cleaning (raise your hand if you’ve been towed). You’d probably be tempted to set up a ping that both repeats and pesters. This way you’d get pestered about it each day to make sure that you don’t forget, and PingMe would reschedule it for next week once you completed the task.

In the past, this feature has been missing from PingMe. When you received a pestering ping that also has a repeat schedule, and you replied with ‘off’ or ‘done’ or ‘stop’ (or ‘ok’ or ‘okay’) to stop the pester, say from your phone, the ping would be marked as done, and it would turn off. For good. But most people really set pester & repeat pings up so that after they turn off the pester for today, they’ll still get the ping tomorrow (or whenever the next scheduled repeat is, if you follow me).

Well now you have options! We’ve changed the behavior of two of the stop words so that they only stop the pester of a ping and not the repeat. As of this morning, replying to a ping with ‘ok’, ‘okay’ or ‘done’ will stop only the pester of a pestering & repeating ping. Replying with ‘stop’ or ‘off’ will turn the ping off as before.

To sum it up, once you go and move your car to the other side of the street, you can reply to the ping ‘ok’ and it will stop bothering you until tomorrow, when it’s time to move your car again.


==> /var/log/pingme/PingMeReceiver.log <==
[INFO] change: 17827, Preparing
[INFO] change: 17827, stripping message part 0...
[INFO] change: 17827, Processing.
[INFO] change: 17827, found a stop message on this line: Ok
[INFO] change: 17827, this is a stop message for ping 16427,
[INFO] change: 17827, user requests to stop pester
[INFO] change: 17827, nagging and recurring ping, clearing events for reschedule

It’s alive!


Usage of TimeWithZone - An Under-Appreciated Notion

2008-02-07 19:00:00 -0500


Over at Ryan’s Scraps, in a post about the new TimeWithZone functionality in edge Rails, there are a pair of comments that I want to highlight. A fella named Ben asks “Couldn’t this be pushed deeper so that current_user.registered_at is a TimeWithZone?”

Then there’s a response from the main guy who developed the TimeWithZone functionality, Geoff Buesig, in regards to how they intend it to be used (and with a bunch of other neat and helpful notes that you should check out):

1.TimeWithZone is similar to the Duration class, in that, you should never need to create an instance directly—in the TWZ case, you’ve got the #in_time_zone, #in_current_time_zone, #change_time_zone and #change_time_zone_to_current methods on Time and DateTime instances that will handle that for you.

So, for example, you can do this:


current_user.registered_at.in_current_time_zone

… and the result will automatically be wrapped in a TimeWithZone

What Ben is asking for, and what Geoff seems to be distancing himself from, is exactly what we here at Zetetic would find incredibly useful: the ability to harness our database backend’s time zone support, PostgreSQL’s ‘timestamp with time zone’.

Here’s the deal. PingMe was designed for users around the globe so it supports time zones. We set it up so that all timestamps (:datetime) were stored in UTC in the database, and converted to the user’s local time on display. We also convert from the user’s local time on datetime input. Nothing fancy or unexpected there, really. And hey, the tzinfo gem supports DST, so we’re good, right?

Well, PingMe is a scheduling system. It has a scheduler daemon that’s constantly checking to see which pings need to be sent out, then it creates outbound events for the dispatcher daemons to deliver. Never mind the terminology, the important thing here is that it’s working in UTC. And that Rails is storing the timestamps in Postgres’ default TIMESTAMP WITHOUT TIME ZONE data type. Here’s an illustrative query:


def lock_a_block(type_name)
before = (Time.now.utc).to_s(:db)

ActiveRecord::Base.connection.execute(
<<-END_OF_SQL
UPDATE events SET dispatcher = '#{@name}'
WHERE id IN (
SELECT e.id FROM
(( events e INNER JOIN targets t ON e.target_id = t.id )
INNER JOIN pings p ON e.ping_id = p.id)
INNER JOIN target_types tt ON t.target_type_id = tt.id
WHERE
tt.const = '#{type_name}'
AND
(
(e.dt_when < '#{before}' AND e.status = '#{Event::STATUS_PENDING}')
OR
(e.retry_at < '#{before}' AND e.status = '#{Event::STATUS_RETRY}')
)
AND e.dispatcher IS NULL
AND t.activated_at IS NOT NULL
AND (p.is_done = 'f' OR p.is_done IS NULL)
AND (p.deleted_at IS NULL)
ORDER BY
e.dt_when ASC
LIMIT #{@block_size}
);
END_OF_SQL
)
end

So the app is providing a UTC timestamp for the before variable, and the timestamps are in UTC in the database. What happens when DST begins or ends? Nothing changes. Everything is sent at the set time, for UTC. So a ping set for 5pm EST was stored at 12:00 UTC, and when 5pm shifts an hour for EDT, that ping is still stored at 12:00 UTC and will be sent either an hour early or an hour late, depending on the circumstance.

The only way we could break this up to work off the time zone setting on the user model is to execute separate queries for all of our users all the time joining against their timezone. Ridiculous! And following Geoff’s notion of things above, it’s just not a clean solution — storing the ping’s time without the time zone is decidedly inaccurate. I hate to say it.

I think the best solution is not to store in UTC here, but to store as a timestamp with time zone. I realize that sounds like an impure solution, but it’s not: PostgreSQL actually stores the data in UTC and can do all sorts of magical conversions for us. We could still use the code above and work in proper UTC, but any DST on the timezone would be respected:


WHERE ... e.dt_when AT TIME ZONE 'UTC' < '#{before}'

And that is why I hope Geoff changes his mind, because we do need TimeWithZone as a data type in Rails, or perhaps a col definition that will provide a TimeWithZone instead of Time objects:


col.datetime :col_name, :with_time_zone => true

As an aside, we don’t leave PingMe users to hang when DST rolls around, we update the relevant time stamps via SQL. But I would like to get us to a better solution. Being able to store TimeWithZone would do just the thing.


Using PingMe With Twitter

2008-01-06 19:00:00 -0500


We’re big Twitter fans, and for quite some time we’ve wanted to allow PingMe users to interact with our service through Twitter. A number of folks have asked for it and, selfishly, we also wanted this capability for ourselves. Now, if you’re a twittaholic, you can access all mobile PingMe functionality straight through the service you know and love.

On a side note, this feature also introduces an alternate way to use SMS messages with PingMe. Previously, in order to send and receive SMS messages with our users (in a cost-efficient way) PingMe would send reminders through a provider’s sms-to-email gateways. These gateway’s are provided by most (but unfortunately not all) cell carriers, and some people pay an extra fee for the capability. Now that we’ve added support Twitter, you can use their service as a universal transport for SMS or even Instant Messaging.

In this post I’ll step you through the process of using PingMe with Twitter. Various details about how messages to create and update pings in this way are covered in older articles and our help section, so I’m going to stick to just the bits pertinent to Twitter.

To get started I’ll assume you already have a Twitter account, and are logged in to their web site. To be able to send get messages from PingMe on twitter, you have to “follow” the PingMe twitter account, ‘gpm’, like so:

Now that you’ve got your twitter account set up, log in to PingMe and click that “Add target” link under the Targets listing on the right side of the page. There’s not much to do but select ‘Twitter’ from the type drop-down and then enter your username on twitter:

Note that if you skipped the first step, where you follow gpm on Twitter, you’ll get an error in that last step.

Now that you’ve got a Twitter target for your account, you can have your pings sent there just like any other target:

So let’s try creating a ping from Twitter. We’ll use the web interface for our example, but keep in mind that you can do this in all the ways you interact with Twitter – including from your phone or IM. What we are doing is sending a direct message to gpm (‘d gpm …’) that contains a create-ping instruction.

The syntax is very similar to the format we use for creating remote pings from e-mail and SMS. The ‘5h’ tells ping me “five hours”, the “p:10” tells us to pester you every ten minutes until you respond with ‘done’, and the ‘t:t’ tells us that you want this ping sent to your Twitter targets (click here for info on setting default targets). The only new trick here is the addition of the ‘+’ sign. Since Twitter is conversational, we have to have a way of distinguishing your create messages from your updates, so after the direct message bit, you begin your ping creation with a plus sign.

Updates, as you might have expected, are simpler. The only caveat is that we don’t necessarily know which ping you are trying to update, so we assume it is the most recent one sent to you. You can send an update like this:

That message will update the most recent ping sent to you to be sent again in 30 minutes.


Updating PingMe's Scheduling System

2008-01-06 19:00:00 -0500


Over the last few months we’ve put a lot of work into PingMe‘s scheduling system because it was necessary if we wanted to expand the service and make it more reliable. Before I get into what we are doing differently I’ll take a moment to describe the previous situation and our setup.

PingMe has a number of daemons – independent processes that are always running, scheduling pings, sending them out, and processing messages that you send to the service. These daemons are implemented in Ruby using Ruby On Rails. This allows them to be tightly coupled with the PingMe web application – the daemon processes and the webapp operate with the same model, which helps us keep the code pretty clean.

The ones of most concern are the dispatchers, the daemons who’s job it was to check for new pings to deliver, and then reschedule them for the next delivery (if necessary). Getting the concurrency right was rather tricky and involved some real nerding out in Postgres (our database engine of choice). Basically, the dispatchers had to do what’s called mutex locking in order to guarantee that different dispatchers would try to send out the same message. The locking code is a neat trick, btw, and it’s still in use, it’s served us well:


def acquire_mutex
ActiveRecord::Base.connection.execute(
<<-END_OF_SQL
LOCK mutex IN ACCESS EXCLUSIVE MODE;
END_OF_SQL
)
end

Different database engines have different facilities for this sort of thing, but basically doing this within a transaction caused the other dispatchers to wait until the lock was released. What were they waiting for? A chance to grab a block of pings to dispatch.

Now the rescheduling of pings, and the scheduling of pings was honestly a not very clean thing to begin with. We had callbacks on the Ping model that would create the actual instances of an outbound message for delivery (we called these Events), and then the dispatchers would need to block those callbacks in certain situations to cause a reschedule. It worked, I don’t want to get into the details of it, but it had one particular problem:

Events are an instance of a Ping associated with a Target for delivery. The one dispatcher we were running would do it’s selection of events to deliver based on target-types. Once we created the new Twitter target type and added a new dispatcher that only handled that one target type (this was all in our dev environment), the daemon would conflict with the other dispatcher. Which ever daemon picked up a ping first for it’s target type and then marked the ping as done was basically preventing the other daemon for processing the ping for its other targets.

The solution was to implement a new daemon, that we called Scheduler, and to move all the rescheduling code into this one serial process. Once we stripped all that out of the Ping model and the dispatcher code, we had a much leaner and faster system. We can now run as many dispatchers as our memory allows and configure them to handle various target types.