summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexei Gradinari <alex2grad@gmail.com>2016-08-18 15:45:59 -0400
committerAlexei Gradinari <alex2grad@gmail.com>2016-09-06 08:58:42 -0500
commit7bb7f7b9d52fdaf50a6e13bbe0529151f920ac08 (patch)
tree015ad65339d18e608323192348a430e9d092f1a4
parente34f299a9672f829ee8d6652d221700fe2c6c56d (diff)
res_pjsip_session: segfault on already disconnected session
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
-rwxr-xr-xconfigure123
-rw-r--r--configure.ac2
-rw-r--r--include/asterisk/autoconfig.h.in3
-rw-r--r--res/res_pjsip_session.c71
-rw-r--r--third-party/pjproject/configure.m41
-rw-r--r--third-party/pjproject/patches/0002-r5435-add-pjsip_inv_session-ref_cnt.patch212
6 files changed, 403 insertions, 9 deletions
diff --git a/configure b/configure
index 3e5140ddb..886ef6f9f 100755
--- a/configure
+++ b/configure
@@ -935,6 +935,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
@@ -11001,6 +11005,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"
@@ -25269,6 +25285,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
@@ -26089,6 +26108,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
diff --git a/configure.ac b/configure.ac
index 950cfc5a1..7fd702fee 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
@@ -2210,6 +2211,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
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index f9a6b46e4..5e7ea7e2a 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -598,6 +598,9 @@
/* 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
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 315393fdb..f54ee9411 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -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;
}
/*!
@@ -2617,7 +2658,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);
+ }
}
}
@@ -2784,7 +2828,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;
@@ -2909,7 +2957,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;
}
diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4
index 67ac04d4d..7a079f657 100644
--- a/third-party/pjproject/configure.m4
+++ b/third-party/pjproject/configure.m4
@@ -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
index 000000000..12ae6a028
--- /dev/null
+++ b/third-party/pjproject/patches/0002-r5435-add-pjsip_inv_session-ref_cnt.patch
@@ -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;
+