[asterisk-users] Asterisk / PHP-AGI / pthreads

Steve Edwards asterisk.org at sedwards.com
Thu Jun 20 12:24:57 CDT 2013


On Thu, 20 Jun 2013, Satish Barot wrote:

> Would you mind sharing a sample of your pthread-ed C AGI? This will help 
> someone like me who has written AGI in Perl/PHP and now exploring C AGI.

The source code for this particular AGI is about 600 lines and uses my own 
AGI library (written before other C AGI libraries were available) so I 
don't think it has a whole lot of value to other programmers.

First a little background on why I used pthreads in this AGI...

The application was an adult chat platform. (Mostly) guys call in to chat 
to (mostly) women and pay $1 to $4 per minute. At the end of the call, the 
accumulated charges are billed to their their credit card.

Charges to credit cards usually take 2 transactions. The first transaction 
('OPEN') places a temporary 'hold' on the card for $XXX. At the end of the 
call, a second transaction ('SALE') finalizes the billing for the actual 
charges for the call.

(This '2 step' process is why you may have trouble charging an expensive 
dinner after checking into a hotel -- the hotel may have placed a 'hold' 
against your credit limit for the expected charge for your entire stay.)

As originally implemented, Asterisk would play a prompt asking the caller 
to enter their card details. After the caller entered a 'valid' card 
number (that passes Luhn mod 10) and expiration date (between the current 
month & current year and current year + 7). My AGI would then play a 
prompt ('Please wait while your card is being verified') and then transmit 
the card details to our card processor. Getting a response from the card 
processor takes a second or so. My AGI returned the card processor 
response code as the 'priority' so the dialplan could continue to the main 
menu or play a prompt indicating why the card details failed. After 3 
failures, a caller was played a prompt instructing them on alternative 
billing methods (high rate NPAs, check by phone, etc).

This client was extremely particular about the 'caller experience' and 
thought the 'second or so' of silence while the card was being verified 
was excessive and had to be eliminated.

Since the card processing time was not under my control, my solution was 
to change my AGI to create a second thread to play the 'please wait' 
prompt while the 'main line' code shipped off the card details to the 
processor.

When I got the processor's response (which was usually before the prompt 
finished playing), the 'main line' code would 'join' the prompt thread. If 
the prompt thread was finished playing the prompt, execution continued 
immediately. If the prompt thread was still playing the prompt, execution 
continued as soon as the prompt thread received the AGI response from 
Asterisk. None of this took any special programming or synchronizing from 
me. It all 'just worked' because of the way pthreads are implemented.

So, what did I learn that would be of value to you? Really understand the 
AGI protocol and you won't make a bunch of silly mistakes.

The AGI protocol is very simple:

1) Read the AGI environment from stdin before you do anything in your AGI 
that interacts with Asterisk.

2) Write your AGI request to stdout.

3) Read Asterisk's AGI response from stdin.

4) Rinse, lather, repeat (steps 2 & 3).

If you use an established AGI library (like you really, really should), 
this should be taken care of automagically, but understanding what's 
really going on will help when things don't work as expected.

The number 1 mistake new AGI programmers make is not reading the AGI 
environment. Sometimes they 'get away' with it, some times they don't.

The number 2 mistake is not reading the responses (step 3). Again, 
sometimes it works, sometimes it doesn't

The number 3 mistake new (and old salts like me still make on occasion) is 
doing any I/O on stdin or stdout. A lot of bad debugging centers around 
'throw in a printf somewhere to see what's going on' instead of using 
'real programmer' tools like gdb*.

If you're using an established AGI library, the crucial relationship 
between steps 2 and 3 will be take care of -- unless you're using multiple 
threads.

If thread 1 issues an AGI request (writing to stdout) and then thread 2 
issues an AGI request (also writing to stdout) before thread 1 reads it's 
response, you've broken the protocol and bad things will happen. Maybe not 
immediately, but certainly when you're demonstrating the system to your 
boss.

So my first suggestion (after using an established library and without 
knowing what your use case is) would be to 'designate' a single thread as 
'the Asterisk' thread and only interact (via stdin and stdout) with 
Asterisk in that thread. If that's not feasible, you're going to need some 
sort of 'semaphore' mechanism so you don't 'intermingle' your AGI requests 
and responses and thereby violate the AGI protocol.

Another couple of suggestions unrelated to threads, but are best (IMNSHO) 
practices for AGIs in general:

1) Use getopt_long(). Most (everybody but me?) seems to like vague, 
conflicting, and impossible to document or remember AGI command line 
options. Seriously, which would you rather see in "the other guy's" 
dialplan 5 years after he's gone:

 	exten = *,n,agi(foo|5555551212|10255|d)

or

 	exten = *,n,agi(foo,--debug,--ani=5555551212,--charge=10255)

2) Use syslog() to say what's going on in your AGI. You can vary the level 
of 'verbosity' by signals or by command line options (see #1).

Sheesh I'm long winded :)

If you still think it would have value to you after reading this email, 
please let me know.

*) A valuable debugging technique is to enable AGI debugging in the 
Asterisk CLI and capture the session when your AGI executes. This file can 
be munged into a file of the AGI environment and the AGI responses. Then, 
when you have your AGI loaded into gdb you can 'run' it specifying this 
file as input ('r <my-agi-input') and step through your AGI with some 
serious debugging horsepower behind you. Note that this technique is 
completely separate from Asterisk and can even take place on a separate 
computer that doesn't even have Asterisk installed.

-- 
Thanks in advance,
-------------------------------------------------------------------------
Steve Edwards       sedwards at sedwards.com      Voice: +1-760-468-3867 PST
Newline                                              Fax: +1-760-731-3000



More information about the asterisk-users mailing list