]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_pjsip_session: segfault on already disconnected session 14/3514/9
authorAlexei Gradinari <alex2grad@gmail.com>
Thu, 18 Aug 2016 19:45:59 +0000 (15:45 -0400)
committerAlexei Gradinari <alex2grad@gmail.com>
Thu, 1 Sep 2016 22:03:59 +0000 (18:03 -0400)
On heavy loaded system the TCP/TLS incoming calls could be
disconnected by pjproject while these calls are being
processed by asterisk which could use the session's memory pools.
If the session in the disconnected state then the session memory
pools were already freed, so we get segfault.

This patch adds a lifetime control on an INVITE session to pjproject.
The lifetime of the session is manipulated by calling
pjsip_inv_add_ref/pjsip_inv_dec_ref.
This patch uses these functions to inform pjproject that the
session is in use.

This patch adds check if the session state is not disconnected
and also checks if the memory pool is not NULL.

This patch also places tasks 'session_end' and 'session_end_completion'
into session's serializer to avoid race condition.

ASTERISK-26291 #close

Change-Id: I4d28b1fb3b91f0492a911d110049d670fdc3c8d7

configure
configure.ac
include/asterisk/autoconfig.h.in
res/res_pjsip_session.c
third-party/pjproject/configure.m4
third-party/pjproject/patches/0002-r5435-add-pjsip_inv_session-ref_cnt.patch [new file with mode: 0644]

index 7f6b5dddfff0381886c6d9799faf446a1a285499..5056debf30aee7b5e05f671f90ad901cbff0df97 100755 (executable)
--- a/configure
+++ b/configure
@@ -915,6 +915,10 @@ PBX_POPT
 POPT_DIR
 POPT_INCLUDE
 POPT_LIB
+PBX_PJSIP_INV_SESSION_REF
+PJSIP_INV_SESSION_REF_DIR
+PJSIP_INV_SESSION_REF_INCLUDE
+PJSIP_INV_SESSION_REF_LIB
 PBX_PJSIP_EVSUB_GRP_LOCK
 PJSIP_EVSUB_GRP_LOCK_DIR
 PJSIP_EVSUB_GRP_LOCK_INCLUDE
@@ -10938,6 +10942,18 @@ PBX_PJSIP_EVSUB_GRP_LOCK=0
 
 
 
+PJSIP_INV_SESSION_REF_DESCRIP="PJSIP INVITE Session Reference Count support"
+PJSIP_INV_SESSION_REF_OPTION=pjsip
+PJSIP_INV_SESSION_REF_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_INV_SESSION_REF=0
+
+
+
+
+
+
+
 
     POPT_DESCRIP="popt"
     POPT_OPTION="popt"
@@ -24863,6 +24879,9 @@ $as_echo "#define HAVE_PJSIP_TLS_TRANSPORT_PROTO 1" >>confdefs.h
 $as_echo "#define HAVE_PJSIP_EVSUB_GRP_LOCK 1" >>confdefs.h
 
 
+$as_echo "#define HAVE_PJSIP_INV_SESSION_REF 1" >>confdefs.h
+
+
    else
 
    if test "x${PBX_PJPROJECT}" != "x1" -a "${USE_PJPROJECT}" != "no"; then
@@ -25683,6 +25702,110 @@ _ACEOF
 fi
 
 
+
+if test "x${PBX_PJSIP_INV_SESSION_REF}" != "x1" -a "${USE_PJSIP_INV_SESSION_REF}" != "no"; then
+   pbxlibdir=""
+   # if --with-PJSIP_INV_SESSION_REF=DIR has been specified, use it.
+   if test "x${PJSIP_INV_SESSION_REF_DIR}" != "x"; then
+      if test -d ${PJSIP_INV_SESSION_REF_DIR}/lib; then
+         pbxlibdir="-L${PJSIP_INV_SESSION_REF_DIR}/lib"
+      else
+         pbxlibdir="-L${PJSIP_INV_SESSION_REF_DIR}"
+      fi
+   fi
+   pbxfuncname="pjsip_inv_add_ref"
+   if test "x${pbxfuncname}" = "x" ; then   # empty lib, assume only headers
+      AST_PJSIP_INV_SESSION_REF_FOUND=yes
+   else
+      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
+      CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
+      as_ac_Lib=`$as_echo "ac_cv_lib_pjsip_${pbxfuncname}" | $as_tr_sh`
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lpjsip" >&5
+$as_echo_n "checking for ${pbxfuncname} in -lpjsip... " >&6; }
+if eval \${$as_ac_Lib+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ${pbxfuncname} ();
+int
+main ()
+{
+return ${pbxfuncname} ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$as_ac_Lib=yes"
+else
+  eval "$as_ac_Lib=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+eval ac_res=\$$as_ac_Lib
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
+  AST_PJSIP_INV_SESSION_REF_FOUND=yes
+else
+  AST_PJSIP_INV_SESSION_REF_FOUND=no
+fi
+
+      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
+   fi
+
+   # now check for the header.
+   if test "${AST_PJSIP_INV_SESSION_REF_FOUND}" = "yes"; then
+      PJSIP_INV_SESSION_REF_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB"
+      # if --with-PJSIP_INV_SESSION_REF=DIR has been specified, use it.
+      if test "x${PJSIP_INV_SESSION_REF_DIR}" != "x"; then
+         PJSIP_INV_SESSION_REF_INCLUDE="-I${PJSIP_INV_SESSION_REF_DIR}/include"
+      fi
+      PJSIP_INV_SESSION_REF_INCLUDE="${PJSIP_INV_SESSION_REF_INCLUDE} $PJPROJECT_CFLAGS"
+      if test "xpjsip.h" = "x" ; then  # no header, assume found
+         PJSIP_INV_SESSION_REF_HEADER_FOUND="1"
+      else                             # check for the header
+         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${PJSIP_INV_SESSION_REF_INCLUDE}"
+         ac_fn_c_check_header_mongrel "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
+if test "x$ac_cv_header_pjsip_h" = xyes; then :
+  PJSIP_INV_SESSION_REF_HEADER_FOUND=1
+else
+  PJSIP_INV_SESSION_REF_HEADER_FOUND=0
+fi
+
+
+         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
+      fi
+      if test "x${PJSIP_INV_SESSION_REF_HEADER_FOUND}" = "x0" ; then
+         PJSIP_INV_SESSION_REF_LIB=""
+         PJSIP_INV_SESSION_REF_INCLUDE=""
+      else
+         if test "x${pbxfuncname}" = "x" ; then                # only checking headers -> no library
+            PJSIP_INV_SESSION_REF_LIB=""
+         fi
+         PBX_PJSIP_INV_SESSION_REF=1
+         cat >>confdefs.h <<_ACEOF
+#define HAVE_PJSIP_INV_SESSION_REF 1
+_ACEOF
+
+      fi
+   fi
+fi
+
+
    fi
 fi
 
index 588a500751fbeb2c5348a706ad549aaf0dc4f544..95c51b76ca4067f9c1f019b79eafd99a13588b26 100644 (file)
@@ -487,6 +487,7 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj_ssl_cert_load_fro
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EXTERNAL_RESOLVER], [PJSIP External Resolver Support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TLS_TRANSPORT_PROTO], [PJSIP TLS Transport proto field support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EVSUB_GRP_LOCK], [PJSIP EVSUB Group Lock support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_INV_SESSION_REF], [PJSIP INVITE Session Reference Count support], [PJPROJECT], [pjsip])
 
 AST_EXT_LIB_SETUP([POPT], [popt], [popt])
 AST_EXT_LIB_SETUP([PORTAUDIO], [PortAudio], [portaudio])
@@ -2190,6 +2191,7 @@ if test "$USE_PJPROJECT" != "no" ; then
       CPPFLAGS="${saved_cppflags}"
 
       AST_EXT_LIB_CHECK([PJSIP_EVSUB_GRP_LOCK], [pjsip], [pjsip_evsub_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+      AST_EXT_LIB_CHECK([PJSIP_INV_SESSION_REF], [pjsip], [pjsip_inv_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
    fi
 fi
 
index 5e53dbc70d98d42fc3ed7ffd621fe1cc48673c8a..337878c4dcaf96315954eb719451dc8396c894a9 100644 (file)
 /* Define if your system has pjsip_get_dest_info declared. */
 #undef HAVE_PJSIP_GET_DEST_INFO
 
+/* Define if your system has PJSIP_INV_SESSION_REF */
+#undef HAVE_PJSIP_INV_SESSION_REF
+
 /* Define if your system has the PJSIP_REPLACE_MEDIA_STREAM headers. */
 #undef HAVE_PJSIP_REPLACE_MEDIA_STREAM
 
index 856626a026bfffa714cbf8530230dde940c2a03b..0509709987f6e70931a96aefa268ea696815b4ae 100644 (file)
@@ -213,6 +213,11 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
        int i;
        int handled = 0;
 
+       if (session->inv_session && session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
+               ast_log(LOG_ERROR, "Failed to handle incoming SDP. Session has been already disconnected\n");
+               return -1;
+       }
+
        for (i = 0; i < sdp->media_count; ++i) {
                /* See if there are registered handlers for this media stream type */
                char media[20];
@@ -2087,6 +2092,16 @@ static int new_invite(void *data)
         * so that we will be notified so we can destroy the session properly
         */
 
+       if (invite->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
+               ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n",
+                       invite->session->inv_session->cause,
+                       pjsip_get_status_text(invite->session->inv_session->cause)->ptr);
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+               pjsip_inv_dec_ref(invite->session->inv_session);
+#endif
+               return -1;
+       }
+
        switch (get_destination(invite->session, invite->rdata)) {
        case SIP_GET_DEST_EXTEN_FOUND:
                /* Things worked. Keep going */
@@ -2097,7 +2112,7 @@ static int new_invite(void *data)
                } else  {
                        pjsip_inv_terminate(invite->session->inv_session, 416, PJ_TRUE);
                }
-               return 0;
+               goto end;
        case SIP_GET_DEST_EXTEN_NOT_FOUND:
        case SIP_GET_DEST_EXTEN_PARTIAL:
        default:
@@ -2110,7 +2125,7 @@ static int new_invite(void *data)
                } else  {
                        pjsip_inv_terminate(invite->session->inv_session, 404, PJ_TRUE);
                }
-               return 0;
+               goto end;
        };
 
        if ((sdp_info = pjsip_rdata_get_sdp_info(invite->rdata)) && (sdp_info->sdp_err == PJ_SUCCESS) && sdp_info->sdp) {
@@ -2120,7 +2135,7 @@ static int new_invite(void *data)
                        } else  {
                                pjsip_inv_terminate(invite->session->inv_session, 488, PJ_TRUE);
                        }
-                       return 0;
+                       goto end;
                }
                /* We are creating a local SDP which is an answer to their offer */
                local = create_local_sdp(invite->session->inv_session, invite->session, sdp_info->sdp);
@@ -2136,7 +2151,7 @@ static int new_invite(void *data)
                } else  {
                        pjsip_inv_terminate(invite->session->inv_session, 500, PJ_TRUE);
                }
-               return 0;
+               goto end;
        } else {
                pjsip_inv_set_local_sdp(invite->session->inv_session, local);
                pjmedia_sdp_neg_set_prefer_remote_codec_order(invite->session->inv_session->neg, PJ_FALSE);
@@ -2153,12 +2168,16 @@ static int new_invite(void *data)
        /* At this point, we've verified what we can, so let's go ahead and send a 100 Trying out */
        if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 100, NULL, NULL, &tdata) != PJ_SUCCESS) {
                pjsip_inv_terminate(invite->session->inv_session, 500, PJ_TRUE);
-               return 0;
+               goto end;
        }
        ast_sip_session_send_response(invite->session, tdata);
 
        handle_incoming_request(invite->session, invite->rdata);
 
+end:
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+       pjsip_inv_dec_ref(invite->session->inv_session);
+#endif
        return 0;
 }
 
@@ -2179,6 +2198,20 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
                return;
        }
 
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+       if (pjsip_inv_add_ref(inv_session) != PJ_SUCCESS) {
+               ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
+               if (inv_session->state != PJSIP_INV_STATE_DISCONNECTED) {
+                       if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
+                               pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+                       } else {
+                               internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
+                       }
+               }
+               return;
+       }
+#endif
+
        session = ast_sip_session_alloc(endpoint, NULL, inv_session, rdata);
        if (!session) {
                if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
@@ -2186,6 +2219,9 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
                } else {
                        internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
                }
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+               pjsip_inv_dec_ref(inv_session);
+#endif
                return;
        }
 
@@ -2196,6 +2232,9 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
                } else {
                        internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
                }
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+               pjsip_inv_dec_ref(inv_session);
+#endif
                ao2_cleanup(invite);
        }
        ao2_ref(session, -1);
@@ -2467,8 +2506,9 @@ static void handle_outgoing(struct ast_sip_session *session, pjsip_tx_data *tdat
        }
 }
 
-static void session_end(struct ast_sip_session *session)
+static int session_end(void *vsession)
 {
+       struct ast_sip_session *session = vsession;
        struct ast_sip_session_supplement *iter;
 
        /* Stop the scheduled termination */
@@ -2480,6 +2520,7 @@ static void session_end(struct ast_sip_session *session)
                        iter->session_end(session);
                }
        }
+       return 0;
 }
 
 /*!
@@ -2585,7 +2626,10 @@ static void session_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
        }
 
        if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
-               session_end(session);
+               if (ast_sip_push_task(session->serializer, session_end, session)) {
+                       /* Do it anyway even though this is not the right thread. */
+                       session_end(session);
+               }
        }
 }
 
@@ -2752,7 +2796,11 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans
                         * Pass the session ref held by session->inv_session to
                         * session_end_completion().
                         */
-                       session_end_completion(session);
+                       if (session
+                               && ast_sip_push_task(session->serializer, session_end_completion, session)) {
+                               /* Do it anyway even though this is not the right thread. */
+                               session_end_completion(session);
+                       }
                        return;
                }
                break;
@@ -2877,7 +2925,12 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru
        static const pj_str_t STR_IP6 = { "IP6", 3 };
        pjmedia_sdp_session *local;
 
-       if (!(local = PJ_POOL_ZALLOC_T(inv->pool_prov, pjmedia_sdp_session))) {
+       if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+               ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n");
+               return NULL;
+       }
+
+       if (!inv->pool_prov || !(local = PJ_POOL_ZALLOC_T(inv->pool_prov, pjmedia_sdp_session))) {
                return NULL;
        }
 
index 67ac04d4d869bf59196f696ee38f2c84b09af037..7a079f6577f5da7ae1a06b5fd059a53e2d32b74e 100644 (file)
@@ -45,4 +45,5 @@ AC_DEFUN([PJPROJECT_CONFIGURE],
        PJPROJECT_SYMBOL_CHECK([PJSIP_EXTERNAL_RESOLVER], [pjsip_endpt_set_ext_resolver], [pjsip.h])
        AC_DEFINE([HAVE_PJSIP_TLS_TRANSPORT_PROTO], 1, [Define if your system has PJSIP_TLS_TRANSPORT_PROTO])
        AC_DEFINE([HAVE_PJSIP_EVSUB_GRP_LOCK], 1, [Define if your system has PJSIP_EVSUB_GRP_LOCK])
+       AC_DEFINE([HAVE_PJSIP_INV_SESSION_REF], 1, [Define if your system has PJSIP_INV_SESSION_REF])
 ])
diff --git a/third-party/pjproject/patches/0002-r5435-add-pjsip_inv_session-ref_cnt.patch b/third-party/pjproject/patches/0002-r5435-add-pjsip_inv_session-ref_cnt.patch
new file mode 100644 (file)
index 0000000..12ae6a0
--- /dev/null
@@ -0,0 +1,212 @@
+When a transport error occured on an INVITE session
+the stack calls on_tsx_state_changed with new state
+PJSIP_INV_STATE_DISCONNECTED and immediately destroys
+the INVITE session.
+At the same time this INVITE session could being processed
+on another thread. This thread could use the session's
+memory pools which were already freed, so we get segfault.
+
+This patch adds a reference counter and new functions:
+pjsip_inv_add_ref and pjsip_inv_dec_ref.
+The INVITE session is destroyed only when the reference
+counter has reached zero.
+
+To avoid race condition an application should call
+pjsip_inv_add_ref/pjsip_inv_dec_ref.
+
+Index: pjsip/include/pjsip-ua/sip_inv.h
+===================================================================
+--- a/pjsip/include/pjsip-ua/sip_inv.h (revision 5434)
++++ b/pjsip/include/pjsip-ua/sip_inv.h (revision 5435)
+@@ -383,6 +383,11 @@
+  * Other applications that want to use these pools must understand
+  * that the flip-flop pool's lifetimes are synchronized to the
+  * SDP offer-answer negotiation.
++ *
++ * The lifetime of this session is controlled by the reference counter in this
++ * structure, which is manipulated by calling #pjsip_inv_add_ref and
++ * #pjsip_inv_dec_ref. When the reference counter has reached zero, then
++ * this session will be destroyed.
+  */
+ struct pjsip_inv_session
+ {
+@@ -412,6 +417,7 @@
+     struct pjsip_timer        *timer;                     /**< Session Timers.    */
+     pj_bool_t          following_fork;            /**< Internal, following
+                                                        forked media?      */
++    pj_atomic_t               *ref_cnt;                   /**< Reference counter. */
+ };
+@@ -631,6 +637,30 @@
+ /**
++ * Add reference counter to the INVITE session. The reference counter controls
++ * the life time of the session, ie. when the counter reaches zero, then it 
++ * will be destroyed.
++ *
++ * @param inv       The INVITE session.
++ * @return          PJ_SUCCESS if the INVITE session reference counter
++ *                  was increased.
++ */
++PJ_DECL(pj_status_t) pjsip_inv_add_ref( pjsip_inv_session *inv );
++
++/**
++ * Decrement reference counter of the INVITE session.
++ * When the session is no longer used, it will be destroyed and
++ * caller is informed with PJ_EGONE return status.
++ *
++ * @param inv       The INVITE session.
++ * @return          PJ_SUCCESS if the INVITE session reference counter
++ *                  was decreased. A status PJ_EGONE will be returned to 
++ *                  inform that session is destroyed.
++ */
++PJ_DECL(pj_status_t) pjsip_inv_dec_ref( pjsip_inv_session *inv );
++
++
++/**
+  * Forcefully terminate and destroy INVITE session, regardless of
+  * the state of the session. Note that this function should only be used
+  * when there is failure in the INVITE session creation. After the
+Index: pjsip/src/pjsip-ua/sip_inv.c
+===================================================================
+--- a/pjsip/src/pjsip-ua/sip_inv.c     (revision 5434)
++++ b/pjsip/src/pjsip-ua/sip_inv.c     (revision 5435)
+@@ -195,6 +195,65 @@
+ }
+ /*
++ * Add reference to INVITE session.
++ */
++PJ_DEF(pj_status_t) pjsip_inv_add_ref( pjsip_inv_session *inv )
++{
++    PJ_ASSERT_RETURN(inv && inv->ref_cnt, PJ_EINVAL);
++
++    pj_atomic_inc(inv->ref_cnt);
++
++    return PJ_SUCCESS;
++}
++
++static void inv_session_destroy(pjsip_inv_session *inv)
++{
++    if (inv->last_ack) {
++      pjsip_tx_data_dec_ref(inv->last_ack);
++      inv->last_ack = NULL;
++    }
++    if (inv->invite_req) {
++      pjsip_tx_data_dec_ref(inv->invite_req);
++      inv->invite_req = NULL;
++    }
++    if (inv->pending_bye) {
++      pjsip_tx_data_dec_ref(inv->pending_bye);
++      inv->pending_bye = NULL;
++    }
++    pjsip_100rel_end_session(inv);
++    pjsip_timer_end_session(inv);
++    pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
++
++    /* Release the flip-flop pools */
++    pj_pool_release(inv->pool_prov);
++    inv->pool_prov = NULL;
++    pj_pool_release(inv->pool_active);
++    inv->pool_active = NULL;
++
++    pj_atomic_destroy(inv->ref_cnt);
++    inv->ref_cnt = NULL;
++}
++
++/*
++ * Decrease INVITE session reference, destroy it when the reference count
++ * reaches zero.
++ */
++PJ_DEF(pj_status_t) pjsip_inv_dec_ref( pjsip_inv_session *inv )
++{
++    pj_atomic_value_t ref_cnt;
++
++    PJ_ASSERT_RETURN(inv && inv->ref_cnt, PJ_EINVAL);
++
++    ref_cnt = pj_atomic_dec_and_get(inv->ref_cnt);
++    pj_assert( ref_cnt >= 0);
++    if (ref_cnt == 0) {
++        inv_session_destroy(inv);
++        return PJ_EGONE;
++    } 
++    return PJ_SUCCESS;    
++}
++
++/*
+  * Set session state.
+  */
+ static void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
+@@ -261,27 +320,7 @@
+     if (inv->state == PJSIP_INV_STATE_DISCONNECTED &&
+       prev_state != PJSIP_INV_STATE_DISCONNECTED) 
+     {
+-      if (inv->last_ack) {
+-          pjsip_tx_data_dec_ref(inv->last_ack);
+-          inv->last_ack = NULL;
+-      }
+-      if (inv->invite_req) {
+-          pjsip_tx_data_dec_ref(inv->invite_req);
+-          inv->invite_req = NULL;
+-      }
+-      if (inv->pending_bye) {
+-          pjsip_tx_data_dec_ref(inv->pending_bye);
+-          inv->pending_bye = NULL;
+-      }
+-      pjsip_100rel_end_session(inv);
+-      pjsip_timer_end_session(inv);
+-      pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
+-
+-      /* Release the flip-flop pools */
+-      pj_pool_release(inv->pool_prov);
+-      inv->pool_prov = NULL;
+-      pj_pool_release(inv->pool_active);
+-      inv->pool_active = NULL;
++      pjsip_inv_dec_ref(inv);
+     }
+ }
+@@ -838,6 +877,12 @@
+     inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
+     pj_assert(inv != NULL);
++    status = pj_atomic_create(dlg->pool, 0, &inv->ref_cnt);
++    if (status != PJ_SUCCESS) {
++      pjsip_dlg_dec_lock(dlg);
++      return status;
++    }
++
+     inv->pool = dlg->pool;
+     inv->role = PJSIP_ROLE_UAC;
+     inv->state = PJSIP_INV_STATE_NULL;
+@@ -881,6 +926,7 @@
+     pjsip_100rel_attach(inv);
+     /* Done */
++    pjsip_inv_add_ref(inv);
+     *p_inv = inv;
+     pjsip_dlg_dec_lock(dlg);
+@@ -1471,6 +1517,12 @@
+     inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
+     pj_assert(inv != NULL);
++    status = pj_atomic_create(dlg->pool, 0, &inv->ref_cnt);
++    if (status != PJ_SUCCESS) {
++      pjsip_dlg_dec_lock(dlg);
++      return status;
++    }
++
+     inv->pool = dlg->pool;
+     inv->role = PJSIP_ROLE_UAS;
+     inv->state = PJSIP_INV_STATE_NULL;
+@@ -1540,6 +1592,7 @@
+     }
+     /* Done */
++    pjsip_inv_add_ref(inv);
+     pjsip_dlg_dec_lock(dlg);
+     *p_inv = inv;