Re: Batch Coding Help Please [LONG]

From: Date: Wed, 27 Jun 2001 05:00:37 +0000
Subject: Re: Batch Coding Help Please [LONG]
References: 1  Groups: php.general 
Request: Send a blank email to php-general+get-55322@lists.php.net to get a copy of this message
> mail($Email, $MsgTitle, "$Message\n\n\n$Content", $Header); Here is the "culprit" -- This is going to fire up a sendmail process for every single email. You are going to choke your server to death with tons and tons of sendmail processes. Actually, after about 50 of these, the mail() function is going to fail. With an "Unable to fork" message, I think. Then, a few of those early sendmails will finish, and your loop will still be going, and some more will go through okay, until you're back up to 50 concurrent sendmails, and some more will choke, and... You'll never be able to keep track of which ones worked. Here is an excerpt from some code I wrote recently. This uses PostgreSQL, so you'll have to adjust it a bit. Hopefully the comments and debug output I've left in there until this is in production awhile will be sufficient as-is. Better more general-purpose code is available in Manuel Lemos' mail class at UpperDesign and several other SMTP email functions/classes. This is pretty raw and unplugged, but that's the way I like it :-) *ALL* of these lines are needed to essentially replace the one line above... <?php # This is to be run as a cron job. # This script: # Opens the SMTP port # Initiates an SQL transaction (thus making this script thread-safe) # Grabs any queued email, in "liame" order, and for each email: # Attempts to send that email via SMTP # Snags the emailid from SMTP # Updates the database with the emailid and marks that email "sent" # # I may also code it to: # Move any sent emails from "queue" to "sent" # Or maybe I'll do that in another script, to reduce execution time of this one # Open SMTP socket: $smtp = fsockopen('localhost', 25, &$errorno, &$errorstr, 2.0); if (!$smtp){ if ($errno){ die("SMPT returned Error #$errno: $errorstr<BR>\n"); } else{ die("Unable to open SMTP<BR>\n"); } } # ... and let SMTP say "Hi": $greeting = fgets($smtp, 1024); switch((int) $greeting){ case 220: # Everything is beautiful echo $greeting, "<BR>\n"; break; default: die("SMTP was expected to greet us, but said<BR>\n$greeting<BR>\n"); break; } echo "Saying HELO<BR>\n"; flush(); # Now, we say "Hi" back: $bytes = fputs($smtp, "HELO listbaby.com\r\n"); if (!$bytes){ die("SMTP wouldn't even take our 'HELO'<BR>\n"); } # ... and let SMTP say, yeah, yeah, let's go: $gotit = fgets($smtp, 1024); switch((int) $gotit){ case 250: # Everything is beautiful echo $gotit, "<BR>\n"; break; default: die("SMTP didn't like us when we said HELO<BR>\n$gotit<BR>\n"); break; } # Now, we're ready to start spewing out the emails! # I'm not real clear if I need both the transaction and SELECT ... FOR UPDATE ... # But I reckon having both won't hurt, eh? # Serializable is "more" transactional than the default: /* But I don't have that in my version... Hopefully you do... $query = "set transaction isolation level serializable"; echo $query, "<BR>\n"; flush(); $serialize = pg_exec($connection, $query); */ # Hmmmm. What I *really* want here is to mark the emails I'm sending # as "pending" right away for all transactions to see, # so if *another* copy of this script runs in parallel, no duplicates are sent... # I'm going to ignore this for now, really, and just not run two in parallel, # because I haven't quite figured out which isolation level does that... $query = "begin transaction"; echo $query, "<BR>\n"; flush(); $transaction = pg_exec($connection, $query); # "for update" locks these rows, so if another "sendmail.htm" runs in the middle of this, we # won't step on our own feet... # Or, at least, that's how I sort of understand it... # liame is "email" backwards -- As I understand it, SMTP can go faster if it your emails # are pre-sorted by destination. IE, all the AOLusers are together, all the Hotmails etc. # So, as a poor man's sort, I keep everybody's email reversed in liame. $query = "select queue_id, email, reply, headers, body from queue where not sent = 't' order by liame "; # Looks like I don't have "for update" in my PostgreSQL either... //$query .= " for update "; echo $query, "<BR>\n"; flush(); $emails = pg_exec($connection, $query); echo "Starting loop<BR>\n"; flush(); $emailcount = 0; while (list($queue_id, $email, $reply, $headers, $body) = @pg_fetch_row($emails, $emailcount++)){ $error = 0; echo "Sending queue_id $queue_id<BR>\n"; flush(); # Tell SMTP who it's from: $command = "MAIL FROM: $reply\r\n"; echo $command, "<BR>\n"; flush(); $mailfrom = fputs($smtp, $command); if (!$mailfrom){ echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like this at all $command<BR>\n"; flush(); $error++; } $mailfrom = fgets($smtp, 1024); switch((int) $mailfrom){ case 250: # Every thing is beautiful echo $mailfrom, "<BR>\n"; flush(); break; default: echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like $command<BR>\n"; echo $mailfrom, "<BR>\n"; flush(); $error++; break; } # Tell SMTP who it's to: $command = "RCPT TO: $email\r\n"; echo $command, "<BR>\n"; flush(); $rcptto = fputs($smtp, $command); if (!$rcptto){ echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like this at all $command<BR>\n"; flush(); $error++; } $rcptto = fgets($smtp, 1024); switch((int) $rcptto){ case 250: # Everything is beautiful echo $rcptto, "<BR>\n"; flush(); break; default: echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like $command<BR>\n"; echo $rcptto, "<BR>\n"; flush(); $error++; break; } # Tell SMTP we're about to spew the actual email: $command = "DATA\r\n"; # No, it's not Star Trek: The Next Generation echo $command, "<BR>\n"; flush(); $data = fputs($smtp, $command); if (!$data){ echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like this at all $command<BR>\n"; flush(); $error++; } $data = fgets($smtp, 1024); switch ((int) $data){ case 354: # Everything is beautiful echo $data, "<BR>\n"; flush(); break; default: echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like $command<BR>\n"; echo $data, "<BR>\n"; flush(); $error++; break; } # The definition of the end of an email for SMTP is a "." all by itself on a line # What if one of our emails actually *HAS* that as part of its content? # I later found out that there's a flag to sendmail to alter the end of message signal # I'm betting there's a SMTP command for that too. # You'll have to find that on your own. $body = str_replace("\n.\n", "\n. \n", $body); $body = ereg_replace("^.\\n", ". \\n", $body); # There's no way our headers should be so messed up, but... # Am I paranoid enough? $headers = str_replace("\n.\n", "\n. \n", $headers); $headers = ereg_replace("^.\\n", ". \\n", $headers); $headout = fputs($smtp, $headers); echo nl2br($headers); flush(); if (!$headout){ echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like these headers:<BR>\n"; echo nl2br($headers); flush(); } $newline = fputs($smtp, "\r\n"); echo "<BR>\n"; $bodyout = fputs($smtp, $body); echo nl2br($body); flush(); if (!$bodyout){ echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like this body:<BR>\n"; echo nl2br($body); flush(); } # End this email: $dotout = fputs($smtp, ".\r\n"); if (!$dotout){ echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't get the email ending...<BR>\n"; flush(); } $sent = fgets($smtp, 1024); if (!$sent){ echo "queue_id $queue_id<BR>\n"; echo "SMTP really didn't like that email.<BR>\n"; flush(); } switch((int) $sent){ case 250: # Everything is beautiful # But let's snag that sendmail unique identifier # So we can literally backtrack to our data $twofifty = strtok($sent, ' '); $sendmail_id = strtok(' '); if (!$error){ $query = "update queue set sent = 't', sendmail_id = '$sendmail_id' where queue_id = $queue_id"; echo $query, "<BR>\n"; flush(); $update = pg_exec($connection, $query); } break; default: echo "queue_id $queue_id<BR>\n"; echo "SMTP didn't like that email.<BR>\n"; flush(); break; } echo "Next<BR>\n"; flush(); } # Tell SMPT we're quitting: $command = "QUIT\r\n"; $quit = fputs($smtp, $command); if (!$quit){ echo "SMTP didn't like $command<BR>\n"; flush(); } $quit = fgets($smtp, 1024); switch ((int) $quit){ case 221: # Everything is beautiful echo $quit, "<BR>\n"; break; default: echo "SMTP didn't like $command<BR>\n"; echo $quit, "<BR>\n"; flush(); break; } $query = "commit transaction"; echo $query, "<BR>\n"; pg_exec($connection, $query); # Close it down: fclose($smtp); ?> -- WARNING richard@zend.com address is an endangered species -- Use ceo@l-i-e.com Wanna help me out? Like Music? Buy a CD: http://l-i-e.com/artists.htm Volunteer a little time: http://chatmusic.com/volunteer.htm

« previous php.general (#55322) next »