dlr-retry-queue patch

"Stevenard, Kevin (Kevin)" <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <B2655CC798AF8C438576129FF8B75DE16F3ED1F7C7@FRMRSSXCHMBSD3.dc-m.alcatel-lucent.com>
Hello Kannel users,

I have written a patch on the smsc_smpp connector to implement a DLR retry queue. In our services we are highly dependent of delivery reports. We were first using a patch (http://www.blogalex.com/wp-content/uploads/2009/05/kannel-dlr-retry.patch) but we were not happy with the following:
- blocking io thread due to the sleep implemented at dlr search level
- preventing instant deliver_sm_resp (due also to the sleep blocking io thread)
- system wide retry, can not be configured per account

As our setup is composed of several servers in a 'cluster' each server is running Kannel with several smpp/emi binds and each server is writing delivery reports in a local MySQL backend. We then use master-master replication to spread dlr entries on all servers.
As we have multiple servers and multiple binds its possible to get a dlr through a deliver_sm before we received the submit_sm_resp containing the message id or before the dlr entry is written and replicated in other db servers (even with a lag < 1sec).

So the patch is adding a queue per smscconn and creates for each of them a dedicated thread to re-process dlr that have not been found in the dlr store, if not configured no queue and no thread are created. 

To configure it:
dlr-retry-count: Number of attempts to process a delivery report if not found in the delivery reports store. Defaults to 0 times (disabled).
dlr-retry-interval: This timer specifies the interval time between delivery reports retries. Defaults to 60 seconds.

#sample in a group = smsc && smsc = smpp:
dlr-retry-interval = 60
dlr-retry-count = 3
dlr-retry-queue.patch (application/octet-stream, 11.3 KB)
Index: gwlib/cfg.def
===================================================================
--- gwlib/cfg.def	(revision 4981)
+++ gwlib/cfg.def	(working copy)
@@ -422,6 +422,8 @@
     OCTSTR(generic-status-sent)
     OCTSTR(generic-status-error)
     OCTSTR(generic-foreign-id-regex)
+    OCTSTR(dlr-retry-count)
+    OCTSTR(dlr-retry-interval)
 )
 
 
Index: doc/userguide/userguide.xml
===================================================================
--- doc/userguide/userguide.xml	(revision 4981)
+++ doc/userguide/userguide.xml	(working copy)
@@ -3576,6 +3576,20 @@
        Defaults to 3.
      </entry></row>
 
+   <row><entry><literal>dlr-retry-count</literal></entry>
+     <entry><literal>number</literal></entry>
+     <entry valign="bottom">
+       Number of attempts to process a delivery report if not found in the delivery
+       reports store. Defaults to 0 times (disabled).
+     </entry></row>
+
+   <row><entry><literal>dlr-retry-interval</literal></entry>
+     <entry><literal>number (seconds)</literal></entry>
+     <entry valign="bottom">
+       This timer specifies the interval time between delivery reports retries.
+       Defaults to 60 seconds.
+     </entry></row>
+
    </tbody></tgroup></informaltable>
 
 </sect2>
Index: gw/smsc/smsc_smpp.c
===================================================================
--- gw/smsc/smsc_smpp.c	(revision 4981)
+++ gw/smsc/smsc_smpp.c	(working copy)
@@ -172,6 +172,10 @@
     int esm_class;
     Load *load;
     SMSCConn *conn;
+    long dlr_retry; /* id of the thread responsible for dlr retries */
+    List *dlr_to_retry; /* list containing dlr waiting for a retry */
+    long dlr_retry_count;
+    long dlr_retry_interval;
 } SMPP;
 
 
@@ -180,6 +184,14 @@
     Msg *msg;
 };
 
+struct dlr_retry {
+    time_t retry_time;
+    long dlr_retry_count;
+    int dlrstat;
+    Octstr *msgid;
+    Octstr *destination_address;
+    Octstr *msgdata;
+};
 
 /*
  * create smpp_msg struct
@@ -211,6 +223,17 @@
     gw_free(msg);
 }
 
+static inline void dlr_retry_destroy(void *arg)
+{
+    struct dlr_retry *dlr;
+    dlr = arg;
+    if (dlr == NULL)
+        return;
+    octstr_destroy(dlr->destination_address);
+    octstr_destroy(dlr->msgdata);
+    octstr_destroy(dlr->msgid);
+    gw_free(dlr);
+}
 
 static SMPP *smpp_create(SMSCConn *conn, Octstr *host, int transmit_port,
                          int receive_port, Octstr *system_type,
@@ -223,7 +246,8 @@
                          Octstr *my_number, int smpp_msg_id_type,
                          int autodetect_addr, Octstr *alt_charset, Octstr *alt_addr_charset,
                          Octstr *service_type, long connection_timeout,
-                         long wait_ack, int wait_ack_action, int esm_class)
+                         long wait_ack, int wait_ack_action, int esm_class,
+                         long dlr_retry_count, long dlr_retry_interval)
 {
     SMPP *smpp;
 
@@ -271,6 +295,10 @@
     smpp->load = load_create_real(0);
     load_add_interval(smpp->load, 1);
     smpp->esm_class = esm_class;
+    smpp->dlr_retry = -1;
+    smpp->dlr_to_retry = gwlist_create();
+    smpp->dlr_retry_count = dlr_retry_count;
+    smpp->dlr_retry_interval = dlr_retry_interval;
 
     return smpp;
 }
@@ -282,6 +310,7 @@
         gw_prioqueue_destroy(smpp->msgs_to_send, msg_destroy_item);
         dict_destroy(smpp->sent_msgs);
         gwlist_destroy(smpp->received_msgs, msg_destroy_item);
+        gwlist_destroy(smpp->dlr_to_retry, dlr_retry_destroy);
         counter_destroy(smpp->message_id_counter);
         octstr_destroy(smpp->host);
         octstr_destroy(smpp->username);
@@ -1475,10 +1504,28 @@
             meta_data_set_value(dlrmsg->sms.meta_data, "smpp", octstr_imm("dlr_err"), network_err, 1);
         }
     } else {
-        error(0,"SMPP[%s]: got DLR but could not find message or was not interested "
-                "in it id<%s> dst<%s>, type<%d>",
-                octstr_get_cstr(smpp->conn->id), octstr_get_cstr(tmp),
-                octstr_get_cstr(destination_addr), dlrstat);
+        if ( smpp->dlr_retry_count > 0 ) {
+            // add the DLR into the retry queue
+            struct dlr_retry *retry = gw_malloc(sizeof(struct dlr_retry));
+            retry->destination_address = octstr_duplicate(destination_addr);
+            retry->dlr_retry_count = 0;
+            retry->dlrstat = dlrstat;
+            retry->msgid = octstr_duplicate(tmp);
+            retry->retry_time = time(NULL) + smpp->dlr_retry_interval;
+
+            retry->msgdata = octstr_duplicate(respstr);
+            gwlist_append(smpp->dlr_to_retry, retry);
+
+            debug("bb.sms.smpp", 0,"SMPP[%s]: Add DLR into the retry queue because it was not found in the DLR storage "
+                    "id<%s> dst<%s>, type<%d>",
+                    octstr_get_cstr(smpp->conn->id), octstr_get_cstr(tmp),
+                    octstr_get_cstr(destination_addr), dlrstat);
+        } else {
+            error(0,"SMPP[%s]: got DLR but could not find message or was not interested "
+                    "in it id<%s> dst<%s>, type<%d>",
+                    octstr_get_cstr(smpp->conn->id), octstr_get_cstr(tmp),
+                    octstr_get_cstr(destination_addr), dlrstat);
+        }
     }
     octstr_destroy(tmp);
     octstr_destroy(network_err);
@@ -1868,8 +1915,76 @@
     return io_arg;
 }
 
+/*
+ * go over all DLRs stored in the dlr_retry list. If some of them
+ * have reached their retry time they are processed again
+ * when a DLR has reached its maximum number of retries it is removed
+ * from the list
+ */
+static void do_dlr_retry_cleanup (SMPP *smpp)
+{
+    long i;
+    Msg *dlrmsg = NULL;
+    for (i = 0; i < gwlist_len(smpp->dlr_to_retry); i++) {
+        struct dlr_retry *dlr;
+        dlr = gwlist_get(smpp->dlr_to_retry, i);
+        if (dlr != NULL ) {
+            if ( difftime(time(NULL),dlr->retry_time) >= 0 ) {
+                debug("bb.sms.smpp", 0,"SMPP[%s]: Attempt <%ld> of <%ld> to process DLR id<%s> dst<%s>, type<%d>",
+                        octstr_get_cstr(smpp->conn->id),
+                        (dlr->dlr_retry_count+1), smpp->dlr_retry_count,
+                        octstr_get_cstr(dlr->msgid),
+                        octstr_get_cstr(dlr->destination_address), dlr->dlrstat);
+                dlrmsg = dlr_find(smpp->conn->id,
+                    dlr->msgid, /* smsc message id */
+                    dlr->destination_address, /* destination */
+                    dlr->dlrstat, 1);
+                if (dlrmsg != NULL) {
+                    debug("bb.sms.smpp", 0,"SMPP[%s]: Found matching DLR id<%s> dst<%s>, type<%d>",
+                            octstr_get_cstr(smpp->conn->id), octstr_get_cstr(dlr->msgid),
+                            octstr_get_cstr(dlr->destination_address), dlr->dlrstat);
+                    dlrmsg->sms.msgdata = octstr_duplicate(dlr->msgdata);
+                    dlrmsg->sms.sms_type = report_mo;
+                    dlrmsg->sms.account = octstr_duplicate(smpp->username);
+                    bb_smscconn_receive(smpp->conn, dlrmsg);
+                    gwlist_delete(smpp->dlr_to_retry,i,1);
+                } else {
+                    dlr->dlr_retry_count++;
+                    if (dlr->dlr_retry_count >= smpp->dlr_retry_count) {
+                        gwlist_delete(smpp->dlr_to_retry,i,1);
+                        error(0,"SMPP[%s]: Number of retry excedeed for DLR "
+                                "id<%s> dst<%s>, type<%d> discarding it",
+                                octstr_get_cstr(smpp->conn->id), octstr_get_cstr(dlr->msgid),
+                                octstr_get_cstr(dlr->destination_address), dlr->dlrstat);
+                    } else {
+                        dlr->retry_time = time(NULL) + smpp->dlr_retry_interval;
+                        debug("bb.sms.smpp", 0,"SMPP[%s]: DLR not yet found re-queueing it id<%s> dst<%s>, type<%d>",
+                                octstr_get_cstr(smpp->conn->id), octstr_get_cstr(dlr->msgid),
+                                octstr_get_cstr(dlr->destination_address), dlr->dlrstat);
+                    }
+                }
+           }
+        }
+    }
+}
 
 /*
+ * execute in a loop do_dlr_retry_cleanup and wait 1sec till the smpp conn is
+ * not quitting
+ */
+static void dlr_retry_thread(void *arg)
+{
+    SMPP *smpp;
+    smpp = arg;
+    /* Make sure we log into our own log-file if defined */
+    log_thread_to(smpp->conn->log_idx);
+    while (!smpp->quitting) {
+        do_dlr_retry_cleanup(smpp);
+        gwthread_sleep(1.0);
+    }
+}
+
+/*
  * sent queue cleanup.
  * @return 1 if io_thread should reconnect; 0 if not
  */
@@ -2060,7 +2175,7 @@
                     break; /* reconnect */
                 time(&last_cleanup);
             }
-            
+
             /* make sure we send */
             if (transmitter && difftime(time(NULL), smpp->throttling_err_time) > SMPP_THROTTLING_SLEEP_TIME) {
                 smpp->throttling_err_time = 0;
@@ -2257,6 +2372,8 @@
     Octstr *alt_addr_charset;
     long connection_timeout, wait_ack, wait_ack_action;
     long esm_class;
+    long dlr_retry_count;
+    long dlr_retry_interval;
 
     my_number = alt_addr_charset = alt_charset = NULL;
     transceiver_mode = 0;
@@ -2401,13 +2518,26 @@
         esm_class = ESM_CLASS_SUBMIT_STORE_AND_FORWARD_MODE;
     }
 
+    /* check for dlr retry count */
+    if (cfg_get_integer(&dlr_retry_count, grp, octstr_imm("dlr-retry-count")) == -1)
+        dlr_retry_count = 0;
+    else if (dlr_retry_count < 0)
+        panic(0, "SMPP: Invalid value for dlr retry count (allowed value >= 0).");
+
+    /* check for dlr retry delay */
+    if (cfg_get_integer(&dlr_retry_interval, grp, octstr_imm("dlr-retry-interval")) == -1)
+        dlr_retry_interval = 60;
+    else if (dlr_retry_interval < 1)
+        panic(0, "SMPP: Invalid value for dlr retry interval (allowed value >= 1).");
+
     smpp = smpp_create(conn, host, port, receive_port, system_type,
                        username, password, address_range,
                        source_addr_ton, source_addr_npi, dest_addr_ton,
                        dest_addr_npi, enquire_link_interval,
                        max_pending_submits, version, priority, validity, my_number,
                        smpp_msg_id_type, autodetect_addr, alt_charset, alt_addr_charset,
-                       service_type, connection_timeout, wait_ack, wait_ack_action, esm_class);
+                       service_type, connection_timeout, wait_ack, wait_ack_action, esm_class,
+                       dlr_retry_count, dlr_retry_interval);
 
     cfg_get_integer(&smpp->bind_addr_ton, grp, octstr_imm("bind-addr-ton"));
     cfg_get_integer(&smpp->bind_addr_npi, grp, octstr_imm("bind-addr-npi"));
@@ -2455,6 +2585,9 @@
     if (receive_port != 0)
         smpp->receiver = gwthread_create(io_thread, io_arg_create(smpp, 0));
 
+    if (smpp->dlr_retry_count > 0)
+        smpp->dlr_retry = gwthread_create(dlr_retry_thread,smpp);
+
     if ((port != 0 && smpp->transmitter == -1) ||
         (receive_port != 0 && smpp->receiver == -1)) {
         error(0, "SMPP[%s]: Couldn't start I/O threads.",
@@ -2468,6 +2601,10 @@
             gwthread_wakeup(smpp->receiver);
             gwthread_join(smpp->receiver);
         }
+        if (smpp->dlr_retry != -1) {
+            gwthread_wakeup(smpp->dlr_retry);
+            gwthread_join(smpp->dlr_retry);
+        }
         smpp_destroy(conn->data);
         conn->data = NULL;
         return -1;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.