[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