As some of you know I recently launched an iPhone application. Part of that application allows for updates to be displayed as a small icon over the large icon launches the application. This is very common among news applications.
To make this work there's a whole lot of internal communication that needs to happen with our website and some other services. I'm happy to report that all of it works, kinda.
Here is the problem that I'm experiencing and it is cron related.
The way it works is very simple. In our system when an article is published I increment a table where we store all of the iPhone IDs.
+----+----------------------------------+---------------+
| id | device_id | article_count |
+----+----------------------------------+---------------+
| 4 | D9E5C452018AA5B7781BDB185D03XXXX | 0 |
I then made a cron hook...
/**
*A cron hook function to run the iphone article count update whenever cron runs
*/
function gpiphone_cron(){
gpiphone_pushBatchNotification();
}
... that runs a simple function to package up all of the iPhone devices with a count greater than zero and send that off to urban airship that does the actual push to the device and then resets the count in the table to zero.
...
/**
* This function pushes the json dictionary to urban airship for processing
* @param dictionary $json A Jason dictionary containing the device aliases and batch and/or message
* The required format appears below
*/
function gpiphone_pushBatchNotification(){
$json = gpiphone_batchJsonArticleCount();
do_post_request("https://go.urbanairship.com/api/push/batch/", $json);
db_query('UPDATE {iphone_pushregistration} SET article_count = %d', 0);
}
/**
* This function compiles a Jason feed for batch processing all of our article updates
* While it takes no arguments it is important to note that we are using device aliases not device tokens to communicate with urban airship
* [{"aliases": [ "D9E5C452018AA5B7781BDB185D03XXXX" ],"aps": { "badge": 1 } },{"aliases": [ "1B789D6D89F0FE1D45B231CE2DCFXXXX" ],"aps": { "badge": 1 } }]
* @return json $json
*/
function gpiphone_batchJsonArticleCount(){
$json = "[";
$sql= "SELECT * FROM {iphone_pushregistration} WHERE article_count > 0 ";
$result = db_query(db_rewrite_sql($sql));
$rows = db_affected_rows($result);
$i = 1;
while($data = db_fetch_object($result)){
$json .='{"aliases": [ "'.$data->device_id.'" ],"aps": { "badge": '.$data->article_count.' } }';
if($i < $rows) {
$json .= ",";
}
$i++;
}
$json .= "]";
return $json;
}
The key problem is that it works, if I run Cron manually. However if I just let Cron do its thing I see that the the table is empty and set to zero but the iPhone is never getting updated with an article count.
So the question is why is Cron running differently when I run it manually then when it runs on its own? I do know that it's running on its own because it's performing a large number of other functions.
Please help me. Is there something that Cron does differently when it is run manually? Can you give me suggestions on how you might triage this problem. I'm really at a loss because everything is working.
I've never really needed to hook cron before and so I'm just not sure if I'm doing it correctly, any help is greatly appreciated.
Greg

Comments
Privs?
Who are you running cron as when you run it "manually"? Who is it running as when it's running "on its own"?
good question
Different users with different permissions would affect the outcome!
Handbook on Platform Cooperativism, a movement building platforms and tools owned by the people. http://bit.ly/hackitownit
good answer
I think you are onto something here. I protected the menu item like so
Does that mean I have to give Cron permission to "access iPhone services" and if so, how do you do that?
I thought these permissions only dealt with functions fired from a URL. Are they tied to the function call, really?
OK, I've narrowed it down, and now it make much less sence.
I put in a bunch of watchdog code and get this
The json obj isn't being built for cron??????
It reaches the cron job, is able to run it, but gets no results to send, and then sends (the empty set) them correctly.
.....So it all working except the problem is that this (below) doesn't run for cron but runs for me(admin). Any Ideas. HELP
/**
* This function compiles a Jason feed for batch processing all of our article updates
* While it takes no arguments it is important to note that we are using device aliases not device tokens to communicate with urban airship
* [{"aliases": [ "D9E5C452018AA5B7781BDB185D038489" ],"aps": { "badge": 1 } },{"aliases": [ "1B789D6D89F0FE1D45B231CE2DCF8A15" ],"aps": { "badge": 1 } }]
* @return json $json
*/
function gpiphone_batchJsonArticleCount(){
}
Any ideas?
You're both right ;)
Short answer: remove db_rewrite_sql(...)
It's not needed.
Long(er) answer:
The default primary table is node and primary field is nid for _db_rewrite_sql, so when the node module's hook_db_rewrite_sql is invoked, its checking for node access permissions, which of course the cron user does not have. Install the devel module and enable display query log; you'll quickly see that what db_rewrite_sql adds to your original query.
Just to hammer it out
Just to hammer it out completely: When you run cron manually it runs as the currently logged in user. When you run cron automatically it runs as UID 0. This is almost always the cause of problems when there's a difference between manual cron runs and automatic ones.
To test you can always log out of your Drupal site and go to /cron.php. You'll see it fail in the same way as the automatic run runs...
Andreashaustrup --- does this
Andreashaustrup --- does this thinking hold true if running cron automatically results in "cron ran successfully" message in Drupal? I am getting that message but update report and feeds aren't updating from a crontab but do when running manually.
Yes
Remember that whatever runs at cron time (the automatic kind) has to be allowed to have anonymous permissions. This can sometimes drive you crazy. For example, I recently used the "access user profiles" for some internal functions that shouldn't be allowed to anonymous users. Unfortuantely, that mean that several cron functions stopped working, not the least of which was the XML sitemap user functions. I had to redesign my other code to not use that permission so that I could get the site map working again. I believe both of the functions you mentioned have permissions that need to be set for anonymous users. However, I rarely ever use Update on a live site; I use it on my development back-ups so that I can test any updates before they go live.
Nancy Dru
Oh wow...wow..wow... three
Oh wow...wow..wow... three weeks and two days later...i learn this. Hip Hip Hooray. When I did the install of cygwin on the Windows 2003 server, the setup.exe package did not display the list of mirrors that are grabbed from the cgwin site. At that time, I had been given server admin privileges for a very brief window (IT guy paranoia) and so I was able to turn off the firewall for a moment to test. Sure enough, the minute it was turned off, the list of mirrors was grabbed properly and I was able to download cygwin packages. I got the idea to test when I discovered that I could use the browser in the server (sneakily as again ..no browser use allowed on this server).... and get the mirror list.
Long story short, I deduced or inferred (which is it?) that this seemed like the same problem -- particularly since I got the "cron ran successfully" message. But this time, I have no ability to turn firewall off to test.
What you folks are saying here makes sense. But why oh why isn't this part of any documentation I've read at Drupal site? Shared hosting folks don't have to deal with this?? I can certainly add it to the cron documentation...seems rather important. :-)
Sooo, I need to create a uid for cron.php and then add that uid to the crontab and problem should be solved, hmm? Hope cause that "sometimes can drive you crazy" behavior is affecting me..
HEY THANKS..and that is definitely a loud thanks.....big, big, big one!!!!
I'm new to cygwin cron but
I'm new to cygwin cron but learning, learning. Since reading this post, I've been looking for the proper syntax to add the user id to my crontab. Is it as simple as adding -u 1 between the Day of Week and Command fields? (won't use user 1)
POSTSCRIPT: found the way to do it It's username=user password=pass for anyone else who stumbles here. Easy enough.
Thanks!
Fixed the problem and I learned something new! Thanks so much for the insight John and the nice triage tip from andreashaugstrup.
To illustrate: drush cron
To illustrate:
drush cron
vs.
drush -u 1 cron
Use 'drush ws' to see recent log entries between runs. :)
If your paying attention: You could put 'drush -u 1 cron' into crontab....
Note: Don't ever do that.
Note: Don't ever do that. :)
You could make a new user specifically to run cron, and then substitute that user's uid.