[svn-commits] russell: branch russell/sla_rewrite r53102 - /team/russell/sla_rewrite/apps/

svn-commits at lists.digium.com svn-commits at lists.digium.com
Thu Feb 1 15:19:49 MST 2007


Author: russell
Date: Thu Feb  1 16:19:48 2007
New Revision: 53102

URL: http://svn.digium.com/view/asterisk?view=rev&rev=53102
Log:
Significantly restructure the SLA implementation ... again.

This new architecture properly handles multiple trunks ringing at the same
time.  Are you wondering why this was a complicated thing to handle?  Well,
let me illustrate a simple, yet complicated scenario.

A call comes in on line2.  This causes outbound calls to station1 and
station2.  However, before either station answers, line1 starts ringing.
Now, when one of the phones answers, it should pick up line1, not line2.
Furthermore, the other phone needs to keep ringing.  And thorough all of this,
all of the blinky lights have to be correct.

Anyway, it's all happy now.

Modified:
    team/russell/sla_rewrite/apps/app_meetme.c

Modified: team/russell/sla_rewrite/apps/app_meetme.c
URL: http://svn.digium.com/view/asterisk/team/russell/sla_rewrite/apps/app_meetme.c?view=diff&rev=53102&r1=53101&r2=53102
==============================================================================
--- team/russell/sla_rewrite/apps/app_meetme.c (original)
+++ team/russell/sla_rewrite/apps/app_meetme.c Thu Feb  1 16:19:48 2007
@@ -356,6 +356,7 @@
 		AST_STRING_FIELD(autocontext);	
 	);
 	AST_LIST_HEAD_NOLOCK(, sla_trunk_ref) trunks;
+	struct ast_dial *dial;
 	struct ast_channel *chan;
 };
 
@@ -391,6 +392,16 @@
 
 static const char sla_registrar[] = "SLA";
 
+static struct sla {
+	pthread_t thread;
+	ast_cond_t cond;
+	ast_mutex_t lock;
+	AST_LIST_HEAD_NOLOCK(, sla_trunk_ref) ringing_trunks;
+	unsigned int stop:1;
+} sla = {
+	.thread = AST_PTHREADT_NULL,
+};
+
 /*! The number of audio buffers to be allocated on pseudo channels
  *  when in a conference */
 static int audio_buffers;
@@ -401,7 +412,7 @@
  *  conversion... the numbers have been modified
  *  to give the user a better level of adjustability
  */
-static signed char gain_map[] = {
+static const char const gain_map[] = {
 	-15,
 	-13,
 	-10,
@@ -458,7 +469,7 @@
 
 static int set_talk_volume(struct ast_conf_user *user, int volume)
 {
-	signed char gain_adjust;
+	char gain_adjust;
 
 	/* attempt to make the adjustment in the channel driver;
 	   if successful, don't adjust in the frame reading routine
@@ -470,7 +481,7 @@
 
 static int set_listen_volume(struct ast_conf_user *user, int volume)
 {
-	signed char gain_adjust;
+	char gain_adjust;
 
 	/* attempt to make the adjustment in the channel driver;
 	   if successful, don't adjust in the frame reading routine
@@ -484,7 +495,7 @@
 {
 	switch (action) {
 	case VOL_UP:
-		switch (vol->desired) {
+		switch (vol->desired) { 
 		case 5:
 			break;
 		case 0:
@@ -2847,6 +2858,234 @@
 	return trunk_ref;
 }
 
+static struct sla_station_ref *create_station_ref(struct sla_station *station)
+{
+	struct sla_station_ref *station_ref;
+
+	if (!(station_ref = ast_calloc(1, sizeof(*station_ref))))
+		return NULL;
+
+	station_ref->station = station;
+
+	return station_ref;
+}
+
+static void change_trunk_state(const struct sla_trunk *trunk, enum sla_trunk_state state)
+{
+	struct sla_station *station;
+	struct sla_trunk_ref *trunk_ref;
+
+	ast_log(LOG_DEBUG, "Setting all refs of trunk %s to state %s\n", trunk->name, trunkstate2str(state));
+
+	AST_LIST_TRAVERSE(&sla_stations, station, entry) {
+		AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+			if (trunk_ref->trunk != trunk)
+				continue;
+			trunk_ref->state = state;
+			ast_device_state_changed("SLA:%s_%s", station->name, trunk->name);
+			break;
+		}
+	}
+}
+
+struct run_station_args {
+	struct sla_station *station;
+	struct sla_trunk *trunk;
+	ast_mutex_t *cond_lock;
+	ast_cond_t *cond;
+};
+
+static void *run_station(void *data)
+{
+	struct sla_station *station;
+	struct sla_trunk *trunk;
+	char conf_name[MAX_CONFNUM];
+	struct ast_flags conf_flags = { 0 };
+	struct ast_conference *conf;
+
+	{
+		struct run_station_args *args = data;
+		station = args->station;
+		trunk = args->trunk;
+		ast_mutex_lock(args->cond_lock);
+		ast_cond_signal(args->cond);
+		ast_mutex_unlock(args->cond_lock);
+		/* args is no longer valid here. */
+	}
+
+	ast_atomic_fetchadd_int((int *) &trunk->active_stations, 1);
+	snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk->name);
+	ast_set_flag(&conf_flags, CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_PASS_DTMF);
+	ast_answer(station->chan);
+	conf = build_conf(conf_name, "", "", 0, 0, 1);
+	if (conf)
+		conf_run(station->chan, conf, conf_flags.flags, NULL);
+	station->chan = NULL;
+	if (ast_atomic_dec_and_test((int *) &trunk->active_stations)) {
+		strncat(conf_name, "|K", sizeof(conf_name) - strlen(conf_name) - 1);
+		admin_exec(NULL, conf_name);
+		change_trunk_state(trunk, SLA_TRUNK_STATE_IDLE);
+	}
+
+	return NULL;
+}
+
+static void *sla_thread(void *data)
+{
+	AST_LIST_HEAD_NOLOCK_STATIC(ringing_stations, sla_station_ref);
+
+	for (; !sla.stop;) {
+		struct sla_trunk_ref *trunk_ref;
+		struct sla_station_ref *station_ref;
+		enum ast_dial_result dial_res = AST_DIAL_RESULT_TRYING;
+
+		ast_mutex_lock(&sla.lock);
+		if (AST_LIST_EMPTY(&sla.ringing_trunks)) {
+			ast_cond_wait(&sla.cond, &sla.lock);
+			if (sla.stop)
+				break;
+			ast_log(LOG_DEBUG, "Ooh, I was woken up!\n");
+		}
+
+		/* At this point, we know there are ringing trunks.  So, make sure that every
+		 * station that uses at least one of the ringing trunks, is ringing. */
+		AST_LIST_TRAVERSE(&sla.ringing_trunks, trunk_ref, entry) {
+			AST_LIST_TRAVERSE(&trunk_ref->trunk->stations, station_ref, entry) {
+				char *tech, *tech_data;
+				struct ast_dial *dial;
+				struct sla_station_ref *ringing_ref;
+				AST_LIST_TRAVERSE(&ringing_stations, ringing_ref, entry) {
+					if (station_ref->station == ringing_ref->station)
+						break;
+				}
+				if (ringing_ref)
+					continue;
+				if (!(dial = ast_dial_create()))
+					continue;
+				tech_data = ast_strdupa(station_ref->station->device);
+				tech = strsep(&tech_data, "/");
+				if (ast_dial_append(dial, tech, tech_data) == -1) {
+					ast_dial_destroy(dial);
+					continue;
+				}
+				if (ast_dial_run(dial, NULL, 1) != AST_DIAL_RESULT_TRYING) {
+					ast_dial_destroy(dial);
+					continue;
+				}
+				if (!(ringing_ref = create_station_ref(station_ref->station))) {
+					ast_dial_join(dial);
+					ast_dial_destroy(dial);
+					continue;
+				}
+				station_ref->station->dial = dial;
+				AST_LIST_INSERT_HEAD(&ringing_stations, ringing_ref, entry);
+				ast_log(LOG_DEBUG, "Started dialing station '%s'\n", station_ref->station->name);
+			}
+		}
+		ast_mutex_unlock(&sla.lock);
+		/* Now, all of the stations that should be ringing, are ringing. */
+		
+		AST_LIST_TRAVERSE_SAFE_BEGIN(&ringing_stations, station_ref, entry) {
+			struct sla_trunk_ref *s_trunk_ref;
+			struct run_station_args args;
+			pthread_attr_t attr;
+			pthread_t dont_care;
+			ast_mutex_t cond_lock;
+			ast_cond_t cond;
+
+			switch ((dial_res = ast_dial_status(station_ref->station->dial))) {
+			case AST_DIAL_RESULT_HANGUP:
+			case AST_DIAL_RESULT_INVALID:
+			case AST_DIAL_RESULT_FAILED:
+			case AST_DIAL_RESULT_TIMEOUT:
+			case AST_DIAL_RESULT_UNANSWERED:
+				AST_LIST_REMOVE_CURRENT(&ringing_stations, entry);
+				ast_dial_join(station_ref->station->dial);
+				ast_dial_destroy(station_ref->station->dial);
+				station_ref->station->dial = NULL;
+				free(station_ref);
+				break;
+			case AST_DIAL_RESULT_ANSWERED:
+				AST_LIST_REMOVE_CURRENT(&ringing_stations, entry);
+				station_ref->station->chan = ast_dial_answered(station_ref->station->dial);
+				/* Find the appropriate trunk to answer. */
+				AST_LIST_TRAVERSE(&station_ref->station->trunks, s_trunk_ref, entry) {
+					ast_mutex_lock(&sla.lock);
+					AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, trunk_ref, entry) {
+						if (s_trunk_ref->trunk == trunk_ref->trunk) {
+							AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry);
+							break;
+						}
+					}
+					AST_LIST_TRAVERSE_SAFE_END
+					ast_mutex_unlock(&sla.lock);
+					if (trunk_ref)
+						break;
+				}
+				if (!trunk_ref) {
+					ast_log(LOG_WARNING, "Found no ringing trunk for station '%s' to answer!\n",
+						station_ref->station->name);
+					break;
+				}
+				ast_answer(trunk_ref->trunk->chan);
+				change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP);
+				args.trunk = trunk_ref->trunk;
+				args.station = station_ref->station;
+				args.cond = &cond;
+				args.cond_lock = &cond_lock;
+				free(trunk_ref);
+				free(station_ref);
+				ast_mutex_init(&cond_lock);
+				ast_cond_init(&cond, NULL);
+				pthread_attr_init(&attr);
+				pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+				ast_mutex_lock(&cond_lock);
+				ast_pthread_create_background(&dont_care, &attr, run_station, &args);
+				ast_cond_wait(&cond, &cond_lock);
+				ast_mutex_unlock(&cond_lock);
+				ast_mutex_destroy(&cond_lock);
+				ast_cond_destroy(&cond);
+				pthread_attr_destroy(&attr);
+				break;
+			case AST_DIAL_RESULT_TRYING:
+			case AST_DIAL_RESULT_RINGING:
+			case AST_DIAL_RESULT_PROGRESS:
+			case AST_DIAL_RESULT_PROCEEDING:
+				break;
+			}
+			if (dial_res == AST_DIAL_RESULT_ANSWERED)
+				break;
+		}
+		AST_LIST_TRAVERSE_SAFE_END
+		if (dial_res != AST_DIAL_RESULT_ANSWERED)
+			continue;
+		/* Find stations that shouldn't be ringing anymore. */
+		AST_LIST_TRAVERSE_SAFE_BEGIN(&ringing_stations, station_ref, entry) {
+			AST_LIST_TRAVERSE(&station_ref->station->trunks, trunk_ref, entry) {
+				struct sla_trunk_ref *ringing_ref;
+				ast_mutex_lock(&sla.lock);
+				AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_ref, entry) {
+					if (trunk_ref->trunk == ringing_ref->trunk)
+						break;
+				}
+				ast_mutex_unlock(&sla.lock);
+				if (ringing_ref)
+					break;
+			}
+			if (!trunk_ref) {
+				AST_LIST_REMOVE_CURRENT(&ringing_stations, entry);
+				ast_dial_join(station_ref->station->dial);
+				ast_dial_destroy(station_ref->station->dial);
+				station_ref->station->dial = NULL;
+				free(station_ref);
+			}
+		}
+		AST_LIST_TRAVERSE_SAFE_END
+	}
+
+	return NULL;
+}
+
 struct dial_trunk_args {
 	struct sla_trunk *trunk;
 	struct sla_station *station;
@@ -2942,24 +3181,6 @@
 	return NULL;
 }
 
-static void change_trunk_state(const struct sla_trunk *trunk, enum sla_trunk_state state)
-{
-	struct sla_station *station;
-	struct sla_trunk_ref *trunk_ref;
-
-	ast_log(LOG_DEBUG, "Setting all refs of trunk %s to state %s\n", trunk->name, trunkstate2str(state));
-
-	AST_LIST_TRAVERSE(&sla_stations, station, entry) {
-		AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
-			if (trunk_ref->trunk != trunk)
-				continue;
-			trunk_ref->state = state;
-			ast_device_state_changed("SLA:%s_%s", station->name, trunk->name);
-			break;
-		}
-	}
-}
-
 static int slastation_exec(struct ast_channel *chan, void *data)
 {
 	char *station_name, *trunk_name;
@@ -3016,6 +3237,7 @@
 		ast_mutex_t cond_lock;
 		ast_cond_t cond;
 		pthread_t dont_care;
+		pthread_attr_t attr;
 		struct dial_trunk_args args = {
 			.trunk = trunk_ref->trunk,
 			.station = station,
@@ -3029,12 +3251,15 @@
 		ast_autoservice_start(chan);
 		ast_mutex_init(&cond_lock);
 		ast_cond_init(&cond, NULL);
+		pthread_attr_init(&attr);
+		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 		ast_mutex_lock(&cond_lock);
-		ast_pthread_create_background(&dont_care, NULL, dial_trunk, &args);
+		ast_pthread_create_background(&dont_care, &attr, dial_trunk, &args);
 		ast_cond_wait(&cond, &cond_lock);
 		ast_mutex_unlock(&cond_lock);
 		ast_mutex_destroy(&cond_lock);
 		ast_cond_destroy(&cond);
+		pthread_attr_destroy(&attr);
 		ast_autoservice_stop(chan);
 		if (!trunk_ref->trunk->chan) {
 			ast_log(LOG_DEBUG, "Trunk didn't get created. chan: %lx\n", (long) trunk_ref->trunk->chan);
@@ -3065,155 +3290,16 @@
 	return 0;
 }
 
-static void *dial_stations(void *data)
-{
-	struct sla_trunk *trunk = data;
-	struct sla_station_ref *station_ref;
-	struct ast_dial **dials;
-	struct sla_trunk_ref **trunk_refs;
-	struct sla_station **stations;
-	struct ast_flags conf_flags = { 0 };
-	struct ast_conference *conf;
-	int res = 0, len, num_dials = 0, i, winner;
-	char conf_name[MAX_CONFNUM];
-
-	len = (trunk->num_stations + 1) * sizeof(*dials);
-	dials = alloca(len);
-	memset(dials, 0, len);
-	trunk_refs = alloca(len);
-	memset(trunk_refs, 0, len);
-	stations = alloca(len);
-	memset(stations, 0, len);
-
-	AST_RWLIST_RDLOCK(&sla_stations);
-	AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry) {
-		struct ast_dial *dial;
-		char *tech, *tech_data;
-		struct sla_trunk_ref *trunk_ref;
-
-		if (!(dial = ast_dial_create())) {
-			res = -1;
-			break;
-		}
-
-		tech_data = ast_strdupa(station_ref->station->device);
-		tech = strsep(&tech_data, "/");
-		ast_dial_append(dial, tech, tech_data);
-
-		AST_LIST_TRAVERSE(&station_ref->station->trunks, trunk_ref, entry) {
-			if (trunk == trunk_ref->trunk)
-				break;
-		}
-		if (!trunk_ref) {
-			res = -1;
-			ast_log(LOG_ERROR, "Trunk referenced this station, "
-				"but the station didn't reference the trunk!\n");
-			break;
-		}
-		trunk_refs[num_dials] = trunk_ref;
-		stations[num_dials] = station_ref->station;
-		dials[num_dials++] = dial;
-	}
-	AST_RWLIST_UNLOCK(&sla_stations);
-
-	if (res) {
-		for (i = 0; dials[i]; i++)
-			ast_dial_destroy(dials[i]);
+static struct sla_trunk_ref *create_trunk_ref(struct sla_trunk *trunk)
+{
+	struct sla_trunk_ref *trunk_ref;
+
+	if (!(trunk_ref = ast_calloc(1, sizeof(*trunk_ref))))
 		return NULL;
-	}
-
-	for (i = 0; dials[i]; i++) {
-		enum ast_dial_result dial_res;
-		dial_res = ast_dial_run(dials[i], NULL, 1);
-		if (dial_res != AST_DIAL_RESULT_TRYING) {
-			ast_log(LOG_ERROR, "Failed to begin dial to station!\n");
-			res = -1;
-			break;
-		}
-	}
-
-	if (res) {
-		for (i = 0; dials[i]; i++) {
-			ast_dial_join(dials[i]);
-			ast_dial_destroy(dials[i]);
-		}
-		return NULL;
-	}
-
-	for (i = 0, winner = -1; ; i = (i + 1) % num_dials) {
-		enum ast_dial_result status;
-		if (!dials[i])
-			continue;
-
-		switch ((status = ast_dial_status(dials[i]))) {
-		case AST_DIAL_RESULT_HANGUP:
-			if (trunk_refs[i]->state != SLA_TRUNK_STATE_IDLE) {
-				trunk_refs[i]->state = SLA_TRUNK_STATE_IDLE;
-				ast_device_state_changed("SLA:%s_%s", stations[i]->name, trunk->name);
-			}
-		case AST_DIAL_RESULT_INVALID:
-		case AST_DIAL_RESULT_FAILED:
-		case AST_DIAL_RESULT_TIMEOUT:
-		case AST_DIAL_RESULT_UNANSWERED:
-			ast_dial_join(dials[i]);
-			ast_dial_destroy(dials[i]);
-			dials[i] = NULL;
-			ast_log(LOG_DEBUG, "breaking #1 i: %d status: %d\n", i, status);
-			break;
-		case AST_DIAL_RESULT_TRYING:
-			break;
-		case AST_DIAL_RESULT_RINGING:
-		case AST_DIAL_RESULT_PROGRESS:
-		case AST_DIAL_RESULT_PROCEEDING:
-			if (trunk_refs[i]->state != SLA_TRUNK_STATE_RINGING) {
-				trunk_refs[i]->state = SLA_TRUNK_STATE_RINGING;
-				ast_device_state_changed("SLA:%s_%s", stations[i]->name, trunk->name);
-			}
-			break;
-		case AST_DIAL_RESULT_ANSWERED:
-			winner = i;
-			ast_answer(trunk->chan);
-			for (i = 0; trunk_refs[i]; i++) {
-				trunk_refs[i]->state = SLA_TRUNK_STATE_UP;
-				ast_device_state_changed("SLA:%s_%s", stations[i]->name, trunk->name);
-			}
-			break;
-		}
-		/* We got a winner, or the trunk hung up. */
-		if (winner > -1 || !trunk->chan)
-			break;
-	}
-
-	for (i = 0; i < num_dials; i++) {
-		if (!dials[i] || i == winner)
-			continue;
-		ast_dial_join(dials[i]);
-		ast_dial_destroy(dials[i]);
-		dials[i] = NULL;
-	}
-
-	if (winner == -1)
-		return NULL;
-
-	ast_atomic_fetchadd_int((int *) &trunk->active_stations, 1);
-	stations[winner]->chan = ast_dial_answered(dials[winner]);
-	ast_set_flag(&conf_flags, CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_PASS_DTMF);
-	snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk->name);
-	conf = build_conf(conf_name, "", "", 0, 0, 1);
-	if (conf)
-		conf_run(stations[winner]->chan, conf, conf_flags.flags, NULL);
-	res = ast_atomic_fetchadd_int((int *) &trunk->active_stations, -1);
-	if (res == 1) {	
-		strncat(conf_name, "|K", sizeof(conf_name) - strlen(conf_name) - 1);
-		admin_exec(NULL, conf_name);
-		change_trunk_state(trunk_refs[winner]->trunk, SLA_TRUNK_STATE_IDLE);
-	}
-	
-	ast_dial_join(dials[winner]);
-	ast_dial_destroy(dials[winner]);
-	stations[winner]->chan = NULL;
-
-	return NULL;
+
+	trunk_ref->trunk = trunk;
+
+	return trunk_ref;
 }
 
 static int slatrunk_exec(struct ast_channel *chan, void *data)
@@ -3223,6 +3309,7 @@
 	struct ast_conference *conf;
 	struct ast_flags conf_flags = { 0 };
 	struct sla_trunk *trunk;
+	struct sla_trunk_ref *trunk_ref;
 
 	AST_RWLIST_RDLOCK(&sla_trunks);
 	trunk = find_trunk(trunk_name);
@@ -3242,21 +3329,27 @@
 	}
 	trunk->chan = chan;
 
+	if (!(trunk_ref = create_trunk_ref(trunk))) {
+		pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
+		return 0;
+	}
+
+	change_trunk_state(trunk, SLA_TRUNK_STATE_RINGING);
+
+	ast_mutex_lock(&sla.lock);
+	if (AST_LIST_EMPTY(&sla.ringing_trunks))
+		ast_cond_signal(&sla.cond);
+	AST_LIST_INSERT_HEAD(&sla.ringing_trunks, trunk_ref, entry);
+	ast_mutex_unlock(&sla.lock);
+
 	snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_name);
 	conf = build_conf(conf_name, "", "", 1, 1, 1);
 	if (!conf) {
 		pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
 		return 0;
 	}
-
-	if (ast_pthread_create_background(&trunk->station_thread, NULL, dial_stations, trunk)) {
-		pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
-		return 0;
-	}
-
 	ast_set_flag(&conf_flags, 
 		CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER | CONFFLAG_PASS_DTMF);
-
 	ast_indicate(chan, AST_CONTROL_RINGING);
 	conf_run(chan, conf, conf_flags.flags, NULL);
 	trunk->chan = NULL;
@@ -3366,6 +3459,17 @@
 	while ((station = AST_RWLIST_REMOVE_HEAD(&sla_stations, entry)))
 		destroy_station(station);
 	AST_RWLIST_UNLOCK(&sla_stations);
+
+	if (sla.thread != AST_PTHREADT_NULL) {
+		ast_mutex_lock(&sla.lock);
+		sla.stop = 1;
+		ast_cond_signal(&sla.cond);
+		ast_mutex_unlock(&sla.lock);
+		pthread_join(sla.thread, NULL);
+	}
+
+	ast_mutex_destroy(&sla.lock);
+	ast_cond_destroy(&sla.cond);
 }
 
 static int check_device(const char *device)
@@ -3478,9 +3582,8 @@
 				ast_log(LOG_ERROR, "Trunk '%s' not found!\n", var->value);
 				continue;
 			}
-			if (!(trunk_ref = ast_calloc(1, sizeof(*trunk_ref))))
+			if (!(trunk_ref = create_trunk_ref(trunk)))
 				continue;
-			trunk_ref->trunk = trunk;
 			trunk_ref->state = SLA_TRUNK_STATE_IDLE;
 			if (!(station_ref = ast_calloc(1, sizeof(*station_ref)))) {
 				free(trunk_ref);
@@ -3585,6 +3688,8 @@
 
 	ast_config_destroy(cfg);
 
+	ast_pthread_create(&sla.thread, NULL, sla_thread, NULL);
+
 	return res;
 }
 



More information about the svn-commits mailing list