diff --git a/ci-scripts/cls_containerize.py b/ci-scripts/cls_containerize.py
index 1d2de33d97b85f94fb8f7b566a43d1d928b4bebd..9570610cd8dc2871d886ca57c42e6af5283f9ed7 100644
--- a/ci-scripts/cls_containerize.py
+++ b/ci-scripts/cls_containerize.py
@@ -933,12 +933,26 @@ class Containerize():
 		logging.debug(cmd)
 		subprocess.run(cmd, shell=True)
 
-		# if the containers are running, recover the logs!
+		# check which containers are running for log recovery later
 		cmd = 'cd ' + self.yamlPath[0] + ' && docker-compose -f docker-compose-ci.yml ps --all'
 		logging.debug(cmd)
-		deployStatus = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, universal_newlines=True, timeout=30)
+		deployStatusLogs = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, universal_newlines=True, timeout=30)
+
+		# Stop the containers to shut down objects
+		logging.debug('\u001B[1m Stopping containers \u001B[0m')
+		cmd = 'cd ' + self.yamlPath[0] + ' && docker-compose -f docker-compose-ci.yml stop'
+		logging.debug(cmd)
+		try:
+			deployStatus = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, universal_newlines=True, timeout=100)
+		except Exception as e:
+			self.exitStatus = 1
+			logging.error('Could not stop containers')
+			HTML.CreateHtmlTestRow('Could not stop', 'KO', CONST.ALL_PROCESSES_OK)
+			logging.error('\u001B[1m Undeploying OAI Object(s) FAILED\u001B[0m')
+			return
+
 		anyLogs = False
-		for state in deployStatus.split('\n'):
+		for state in deployStatusLogs.split('\n'):
 			res = re.search('Name|----------', state)
 			if res is not None:
 				continue
diff --git a/ci-scripts/conf_files/episci/episci_gnb.band78.sa.fr1.106PRB.usrpn310.conf b/ci-scripts/conf_files/episci/episci_gnb.band78.sa.fr1.106PRB.usrpn310.conf
index a648db0efece1d80a10b665495cbee2453d2a5b9..e92585b5feb90d54224b76d94b8a1f60444d48a1 100644
--- a/ci-scripts/conf_files/episci/episci_gnb.band78.sa.fr1.106PRB.usrpn310.conf
+++ b/ci-scripts/conf_files/episci/episci_gnb.band78.sa.fr1.106PRB.usrpn310.conf
@@ -214,52 +214,6 @@ MACRLCs = (
         }
 );
 
-L1s = (
-{
-  num_cc = 1;
-  tr_n_preference = "local_mac";
-  thread_pool_size = 8;
-  prach_dtx_threshold = 120;
-  pucch0_dtx_threshold = 150;
-}
-);
-
-RUs = (
-    {
-       local_rf       = "yes"
-         nb_tx          = 1
-         nb_rx          = 1
-         att_tx         = 0
-         att_rx         = 0;
-         bands          = [78];
-         max_pdschReferenceSignalPower = -27;
-         max_rxgain                    = 75;
-         eNB_instances  = [0];
-         #beamforming 1x4 matrix:
-         bf_weights = [0x00007fff, 0x0000, 0x0000, 0x0000];
-         #clock_src = "external";
-         sdr_addrs = "mgmt_addr=172.21.19.14,addr=192.168.10.2,second_addr=192.168.20.2,clock_source=internal,time_source=internal"
-    }
-);
-
-THREAD_STRUCT = (
-  {
-    #three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
-    parallel_config    = "PARALLEL_SINGLE_THREAD";
-    #two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
-    worker_config      = "WORKER_ENABLE";
-  }
-);
-
-rfsimulator :
-{
-    serveraddr = "server";
-    serverport = "4043";
-    options = (); #("saviq"); or/and "chanmod"
-    modelname = "AWGN";
-    IQfile = "/tmp/rfsimulator.iqs";
-};
-
 security = {
   # preferred ciphering algorithms
   # the first one of the list that an UE supports in chosen
diff --git a/ci-scripts/conf_files/episci/episci_rcc.band78.tm1.106PRB.nfapi.conf b/ci-scripts/conf_files/episci/episci_rcc.band78.tm1.106PRB.nfapi.conf
index 0db6ac9036b143ae162b2e573006b5778dbb2832..354a1eb07665095c798fd8d7c709a8dab05119ed 100644
--- a/ci-scripts/conf_files/episci/episci_rcc.band78.tm1.106PRB.nfapi.conf
+++ b/ci-scripts/conf_files/episci/episci_rcc.band78.tm1.106PRB.nfapi.conf
@@ -218,16 +218,6 @@ MACRLCs = (
         }
 )
 
-
-THREAD_STRUCT = (
-  {
-    #three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
-    parallel_config    = "PARALLEL_RU_L1_TRX_SPLIT";
-    #two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
-    worker_config      = "WORKER_ENABLE";
-  }
-);
-
 log_config =
     {
       global_log_level                      ="info";
diff --git a/ci-scripts/conf_files/episci/proxy_gnb.band78.sa.fr1.106PRB.usrpn310.conf b/ci-scripts/conf_files/episci/proxy_gnb.band78.sa.fr1.106PRB.usrpn310.conf
index 84cb18d435f613576d80afd25b07e0c0939f0c90..7b6e045ffc2755a724b2a23d7beffe503a9cdf2f 100644
--- a/ci-scripts/conf_files/episci/proxy_gnb.band78.sa.fr1.106PRB.usrpn310.conf
+++ b/ci-scripts/conf_files/episci/proxy_gnb.band78.sa.fr1.106PRB.usrpn310.conf
@@ -215,52 +215,6 @@ MACRLCs = (
         }
 );
 
-L1s = (
-{
-  num_cc = 1;
-  tr_n_preference = "local_mac";
-  thread_pool_size = 8;
-  prach_dtx_threshold = 120;
-  pucch0_dtx_threshold = 150;
-}
-);
-
-RUs = (
-    {
-       local_rf       = "yes"
-         nb_tx          = 1
-         nb_rx          = 1
-         att_tx         = 0
-         att_rx         = 0;
-         bands          = [78];
-         max_pdschReferenceSignalPower = -27;
-         max_rxgain                    = 75;
-         eNB_instances  = [0];
-         #beamforming 1x4 matrix:
-         bf_weights = [0x00007fff, 0x0000, 0x0000, 0x0000];
-         #clock_src = "external";
-         sdr_addrs = "mgmt_addr=172.21.19.14,addr=192.168.10.2,second_addr=192.168.20.2,clock_source=internal,time_source=internal"
-    }
-);
-
-THREAD_STRUCT = (
-  {
-    #three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
-    parallel_config    = "PARALLEL_SINGLE_THREAD";
-    #two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
-    worker_config      = "WORKER_ENABLE";
-  }
-);
-
-rfsimulator :
-{
-    serveraddr = "server";
-    serverport = "4043";
-    options = (); #("saviq"); or/and "chanmod"
-    modelname = "AWGN";
-    IQfile = "/tmp/rfsimulator.iqs";
-};
-
 security = {
   # preferred ciphering algorithms
   # the first one of the list that an UE supports in chosen
diff --git a/ci-scripts/conf_files/episci/proxy_rcc.band78.tm1.106PRB.nfapi.conf b/ci-scripts/conf_files/episci/proxy_rcc.band78.tm1.106PRB.nfapi.conf
index 270ad7584578c938b41254d62a72abaedf7729dc..41be848ef53942be7647dc9ad42d71997ef5fd84 100644
--- a/ci-scripts/conf_files/episci/proxy_rcc.band78.tm1.106PRB.nfapi.conf
+++ b/ci-scripts/conf_files/episci/proxy_rcc.band78.tm1.106PRB.nfapi.conf
@@ -235,16 +235,6 @@ MACRLCs = (
         }
 )
 
-
-THREAD_STRUCT = (
-  {
-    #three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
-    parallel_config    = "PARALLEL_RU_L1_TRX_SPLIT";
-    #two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
-    worker_config      = "WORKER_ENABLE";
-  }
-);
-
 log_config =
     {
       global_log_level                      ="info";
diff --git a/ci-scripts/constants.py b/ci-scripts/constants.py
index 4f82811ac1e8ee93067ea76bd40ffe200f3564bb..a7ce01615eb9703a12be349317a2570dc988496c 100644
--- a/ci-scripts/constants.py
+++ b/ci-scripts/constants.py
@@ -46,6 +46,7 @@ ENB_PROCESS_NOLOGFILE_TO_ANALYZE = -14
 ENB_PROCESS_SLAVE_RRU_NOT_SYNCED = -15
 ENB_REAL_TIME_PROCESSING_ISSUE = -16
 ENB_RETX_ISSUE = -17
+ENB_SHUTDOWN_NO_BYE = -18
 HSS_PROCESS_FAILED = -2
 HSS_PROCESS_OK = +2
 MME_PROCESS_FAILED = -3
diff --git a/ci-scripts/ran.py b/ci-scripts/ran.py
index a576054370da38ab8906a046425604e9a64b587c..b9e7eb476efa5035be86df4fc8bc682217371c75 100644
--- a/ci-scripts/ran.py
+++ b/ci-scripts/ran.py
@@ -812,6 +812,7 @@ class RANManagement():
 		ULRetxIssue = False
 		nrRrcRcfgComplete = 0
 		harqFeedbackPast = 0
+		showedByeMsg = False # last line is Bye. -> stopped properly
 	
 		line_cnt=0 #log file line counter
 		for line in enb_log_file.readlines():
@@ -1024,6 +1025,12 @@ class RANManagement():
 				if result is not None:
 					gnb_markers[k].append(line_cnt)
 
+			# check whether e/gNB log finishes with "Bye." message
+			# Note that it is "=" not "|=" so not only is the regex
+			# asking for EOF (\Z) but we also only retain the last
+			# line's result
+			showedByeMsg = re.search(r'^Bye.\n\Z', str(line), re.MULTILINE) is not None
+
 		enb_log_file.close()
 
 
@@ -1208,6 +1215,13 @@ class RANManagement():
 				logging.debug('No real time stats found in the log file')
 				htmleNBFailureMsg += statMsg
 
+			if not showedByeMsg:
+				logging.debug('\u001B[1;37;41m ' + nodeB_prefix + 'NB did not show "Bye." message at end, it likely did not stop properly! \u001B[0m')
+				htmleNBFailureMsg += 'No Bye. message found, did not stop properly\n'
+				global_status = CONST.ENB_SHUTDOWN_NO_BYE
+			else:
+				logging.debug('"Bye." message found at end.')
+
 		else:
 			#Removing UE log
 			statMsg = '[MAC] Removing UE msg count =  '+str(removing_ue)
diff --git a/common/utils/LOG/log.c b/common/utils/LOG/log.c
index 9e8e17a3a4a7203231c7830a84367578afce6f09..2098a33c204b8db3720d7ba7662fa66ee8fa4c52 100644
--- a/common/utils/LOG/log.c
+++ b/common/utils/LOG/log.c
@@ -801,15 +801,13 @@ void logClean (void)
   int i;
 
   if(isLogInitDone()) {
-    LOG_UI(PHY,"\n");
-
     for (i=MIN_LOG_COMPONENTS; i < MAX_LOG_COMPONENTS; i++) {
       close_component_filelog(i);
     }
   }
 }
 
-extern volatile int oai_exit;//extern int oai_exit;
+extern int oai_exit;
 void flush_mem_to_file(void)
 {
   int fp;
diff --git a/common/utils/T/T.c b/common/utils/T/T.c
index 2d724ffd0024dd08c9a25663a5039cebbb3df642..6ba1962d1ffeada20bf5cb4984a939c6ba4c508b 100644
--- a/common/utils/T/T.c
+++ b/common/utils/T/T.c
@@ -8,22 +8,19 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <sys/socket.h>
+#include <sys/types.h>
+#include <signal.h>
 
 #include "common/config/config_userapi.h"
 
-#define QUIT(x) do { \
-    printf("T tracer: QUIT: %s\n", x); \
-    exit(1); \
-  } while (0)
-
 /* array used to activate/disactivate a log */
 static int T_IDs[T_NUMBER_OF_IDS];
 int *T_active = T_IDs;
 
 int T_stdout = 1;
 
-
 static int T_socket;
+static int local_tracer_pid;
 
 /* T_cache
  * - the T macro picks up the head of freelist and marks it busy
@@ -33,63 +30,78 @@ volatile int _T_freelist_head;
 volatile int *T_freelist_head = &_T_freelist_head;
 T_cache_t *T_cache;
 
-static void get_message(int s) {
+#define GET_MESSAGE_FAIL do { \
+    printf("T tracer: get_message failed\n"); \
+    return -1; \
+  } while (0)
+
+/* return -1 if error, 0 if no error */
+static int get_message(int s)
+{
   char t;
   int l;
   int id;
   int is_on;
 
-  if (read(s, &t, 1) != 1) QUIT("get_message fails");
+  if (read(s, &t, 1) != 1) GET_MESSAGE_FAIL;
 
   printf("T tracer: got mess %d\n", t);
 
   switch (t) {
     case 0:
-
       /* toggle all those IDs */
       /* optimze? (too much syscalls) */
-      if (read(s, &l, sizeof(int)) != sizeof(int)) QUIT("get_message fails");
+      if (read(s, &l, sizeof(int)) != sizeof(int)) GET_MESSAGE_FAIL;
 
       while (l) {
-        if (read(s, &id, sizeof(int)) != sizeof(int)) QUIT("get_message fails");
+        if (read(s, &id, sizeof(int)) != sizeof(int)) GET_MESSAGE_FAIL;
 
         T_IDs[id] = 1 - T_IDs[id];
         l--;
       }
-
       break;
-
     case 1:
-
       /* set IDs as given */
       /* optimize? */
-      if (read(s, &l, sizeof(int)) != sizeof(int)) QUIT("get_message fails");
+      if (read(s, &l, sizeof(int)) != sizeof(int)) GET_MESSAGE_FAIL;
 
       id = 0;
 
       while (l) {
         if (read(s, &is_on, sizeof(int)) != sizeof(int))
-          QUIT("get_message fails");
+          GET_MESSAGE_FAIL;
 
         T_IDs[id] = is_on;
         id++;
         l--;
       }
-
       break;
-
     case 2:
       break; /* do nothing, this message is to wait for local tracer */
   }
+
+  return 0;
 }
 
-static void *T_receive_thread(void *_) {
-  while (1) get_message(T_socket);
+static void *T_receive_thread(void *_)
+{
+  int err = 0;
+  while (!err) err = get_message(T_socket);
+
+  printf("T tracer: fatal: T tracer disabled\n");
+
+  shutdown(T_socket, SHUT_RDWR);
+  close(T_socket);
+  kill(local_tracer_pid, SIGKILL);
+
+  /* disable all traces */
+  memset(T_IDs, 0, sizeof(T_IDs));
 
   return NULL;
 }
 
-static void new_thread(void *(*f)(void *), void *data) {
+static void new_thread(void *(*f)(void *), void *data)
+{
   pthread_t t;
   pthread_attr_t att;
 
@@ -118,27 +130,11 @@ static void new_thread(void *(*f)(void *), void *data) {
 void T_local_tracer_main(int remote_port, int wait_for_tracer,
                          int local_socket, void *shm_array);
 
-/* We monitor the tracee and the local tracer processes.
- * When one dies we forcefully kill the other.
- */
-#include <sys/types.h>
-#include <sys/wait.h>
-static void monitor_and_kill(int child1, int child2) {
-  int child;
-  int status;
-  child = wait(&status);
-
-  if (child == -1) perror("wait");
-
-  kill(child1, SIGKILL);
-  kill(child2, SIGKILL);
-  exit(0);
-}
-
-void T_init(int remote_port, int wait_for_tracer, int dont_fork) {
+void T_init(int remote_port, int wait_for_tracer)
+{
   int socket_pair[2];
   int s;
-  int child1, child2;
+  int child;
   int i;
 
   if (socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair)) {
@@ -162,47 +158,40 @@ void T_init(int remote_port, int wait_for_tracer, int dont_fork) {
 
   for (i = 0; i < T_CACHE_SIZE; i++) T_cache[i].busy = 0;
 
-  /* child1 runs the local tracer and child2 (or main) runs the tracee */
-  child1 = fork();
+  /* child runs the local tracer and main runs the tracee */
+  child = fork();
 
-  if (child1 == -1) abort();
+  if (child == -1) abort();
 
-  if (child1 == 0) {
+  if (child == 0) {
     close(socket_pair[1]);
     T_local_tracer_main(remote_port, wait_for_tracer, socket_pair[0],
                         T_cache);
     exit(0);
   }
 
-  close(socket_pair[0]);
+  local_tracer_pid = child;
 
-  if (dont_fork == 0) {
-    child2 = fork();
+  close(socket_pair[0]);
 
-    if (child2 == -1) abort();
+  s = socket_pair[1];
+  T_socket = s;
 
-    if (child2 != 0) {
-      close(socket_pair[1]);
-      munmap(T_cache, T_CACHE_SIZE * sizeof(T_cache_t));
-      monitor_and_kill(child1, child2);
-    }
+  /* wait for first message - initial list of active T events */
+  if (get_message(s) == -1) {
+    kill(local_tracer_pid, SIGKILL);
+    return;
   }
 
-  s = socket_pair[1];
-  /* wait for first message - initial list of active T events */
-  get_message(s);
-  T_socket = s;
   new_thread(T_receive_thread, NULL);
 }
 
-void T_Config_Init(void) {
-  int T_port=TTRACER_DEFAULT_PORTNUM; /* by default we wait for the tracer */
-  int T_nowait=0;                     /* default port to listen to to wait for the tracer */
-  int T_dont_fork=0;                  /* default is to fork, see 'T_init' to understand */
+void T_Config_Init(void)
+{
+  int T_port = TTRACER_DEFAULT_PORTNUM;
+  int T_nowait = 0;
   paramdef_t ttraceparams[] = CMDLINE_TTRACEPARAMS_DESC;
-  /* for a cleaner config file, TTracer params should be defined in a
-   * specific section...
-   */
+
   config_get(ttraceparams,
              sizeof(ttraceparams) / sizeof(paramdef_t),
              TTRACER_CONFIG_PREFIX);
@@ -217,5 +206,5 @@ void T_Config_Init(void) {
   }
 
   if (T_stdout == 0 || T_stdout == 2)
-    T_init(T_port, 1-T_nowait, T_dont_fork);
+    T_init(T_port, 1-T_nowait);
 }
diff --git a/common/utils/T/T.h b/common/utils/T/T.h
index df1e95d2df3289f2919b76f070a5a1fa88ad52f2..dd698906c83c1a61b5ff75af2fa406ef2eb12d9e 100644
--- a/common/utils/T/T.h
+++ b/common/utils/T/T.h
@@ -15,8 +15,8 @@
 #include "T_IDs.h"
 
 #define T_ACTIVE_STDOUT  2
-/* known type - this is where you add new types */
 
+/* known type - this is where you add new types */
 #define T_INT(x) int, (x)
 #define T_FLOAT(x) float, (x)
 #define T_BUFFER(x, len) buffer, ((T_buffer){addr:(x), length:(len)})
@@ -111,6 +111,7 @@ typedef struct {
 extern volatile int *T_freelist_head;
 extern T_cache_t *T_cache;
 extern int *T_active;
+
 #define T_GET_SLOT \
   T_LOCAL_busy = __sync_fetch_and_or(&T_cache[T_LOCAL_slot].busy, 0x01);
 
@@ -559,14 +560,12 @@ extern int *T_active;
     } \
   } while (0)
 
-
 #define CONFIG_HLP_TPORT         "tracer port\n"
 #define CONFIG_HLP_NOTWAIT       "don't wait for tracer, start immediately\n"
-#define CONFIG_HLP_TNOFORK       "to ease debugging with gdb\n"
 #define CONFIG_HLP_STDOUT        "print log messges on console\n"
 
-
 #define TTRACER_CONFIG_PREFIX   "TTracer"
+
 /*-------------------------------------------------------------------------------------------------------------------------------------------------*/
 /*                                            configuration parameters for TTRACE utility                                                          */
 /*   optname                     helpstr                paramflags           XXXptr           defXXXval                         type       numelt  */
@@ -575,15 +574,12 @@ extern int *T_active;
 #define CMDLINE_TTRACEPARAMS_DESC {  \
     {"T_port",                     CONFIG_HLP_TPORT,      0,                iptr:&T_port,        defintval:TTRACER_DEFAULT_PORTNUM, TYPE_INT,   0},\
     {"T_nowait",                   CONFIG_HLP_NOTWAIT,    PARAMFLAG_BOOL,   iptr:&T_nowait,      defintval:0,                       TYPE_INT,   0},\
-    {"T_dont_fork",                CONFIG_HLP_TNOFORK,    PARAMFLAG_BOOL,   iptr:&T_dont_fork,   defintval:0,                       TYPE_INT,   0},\
     {"T_stdout",                   CONFIG_HLP_STDOUT,     0,                iptr:&T_stdout,      defintval:1,                       TYPE_INT,   0},\
   }
 
-
-
-/* log on stdout */
-void T_init(int remote_port, int wait_for_tracer, int dont_fork);
+void T_init(int remote_port, int wait_for_tracer);
 void T_Config_Init(void);
+
 #else /* T_TRACER */
 
 /* if T_TRACER is not defined or is 0, the T is deactivated */
diff --git a/common/utils/ocp_itti/intertask_interface.cpp b/common/utils/ocp_itti/intertask_interface.cpp
index 43001ec95b2f3a549365583fa2540c06649e55ce..4613cd5c1b8953a26827575b28e69a70220959e5 100644
--- a/common/utils/ocp_itti/intertask_interface.cpp
+++ b/common/utils/ocp_itti/intertask_interface.cpp
@@ -438,23 +438,24 @@ extern "C" {
   void itti_send_terminate_message(task_id_t task_id) {
   }
 
-  static volatile bool shutting_down;
+  pthread_mutex_t signal_mutex;
 
   static void catch_sigterm(int) {
     static const char msg[] = "\n** Caught SIGTERM, shutting down\n";
     __attribute__((unused))
     int unused = write(STDOUT_FILENO, msg, sizeof(msg) - 1);
-    shutting_down = true;
+    pthread_mutex_unlock(&signal_mutex);
   }
 
   void itti_wait_tasks_end(void) {
-    shutting_down = false;
+
+    pthread_mutex_init(&signal_mutex, NULL);
+    pthread_mutex_lock(&signal_mutex);
+
     signal(SIGTERM, catch_sigterm);
-    //signal(SIGINT, catch_sigterm);
-    while (! shutting_down)
-    {
-      sleep(24 * 3600);
-    }
+    signal(SIGINT, catch_sigterm);
+
+    pthread_mutex_lock(&signal_mutex);
   }
 
   void itti_update_lte_time(uint32_t frame, uint8_t slot) {}
diff --git a/common/utils/system.c b/common/utils/system.c
index 00839b485ca15aa0534c2cfa52d1421ccc375c71..f13fd842892d8a4426ce65ab294aa508d370b3c2 100644
--- a/common/utils/system.c
+++ b/common/utils/system.c
@@ -231,8 +231,6 @@ void threadCreate(pthread_t* t, void * (*func)(void*), void * param, char* name,
   int settingPriority = 1;
   ret=pthread_attr_init(&attr);
   AssertFatal(ret==0,"ret: %d, errno: %d\n",ret, errno);
-  ret=pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-  AssertFatal(ret==0,"ret: %d, errno: %d\n",ret, errno);
   
   if (checkIfFedoraDistribution())
     if (checkIfGenericKernelOnFedora())
diff --git a/common/utils/threadPool/thread-pool.c b/common/utils/threadPool/thread-pool.c
index de9091de7934b4126c727f6a928263ff27d541cd..d1af56b4775dd8d4374881eb7a8c7c5a029c662a 100644
--- a/common/utils/threadPool/thread-pool.c
+++ b/common/utils/threadPool/thread-pool.c
@@ -48,9 +48,14 @@ void displayList(notifiedFIFO_t *nf) {
 static inline  notifiedFIFO_elt_t *pullNotifiedFifoRemember( notifiedFIFO_t *nf, struct one_thread *thr) {
   mutexlock(nf->lockF);
 
-  while(!nf->outF)
+  while(!nf->outF && !thr->terminate)
     condwait(nf->notifF, nf->lockF);
 
+  if (thr->terminate) {
+    mutexunlock(nf->lockF);
+    return NULL;
+  }
+
   notifiedFIFO_elt_t *ret=nf->outF;
   nf->outF=nf->outF->next;
 
@@ -59,7 +64,7 @@ static inline  notifiedFIFO_elt_t *pullNotifiedFifoRemember( notifiedFIFO_t *nf,
 
   // For abort feature
   thr->runningOnKey=ret->key;
-  thr->abortFlag=false;
+  thr->dropJob = false;
   mutexunlock(nf->lockF);
   return ret;
 }
@@ -71,6 +76,10 @@ void *one_thread(void *arg) {
   // Infinite loop to process requests
   do {
     notifiedFIFO_elt_t *elt=pullNotifiedFifoRemember(&tp->incomingFifo, myThread);
+    if (elt == NULL) {
+      AssertFatal(myThread->terminate, "pullNotifiedFifoRemember() returned NULL although thread not aborted\n");
+      break;
+    }
 
     if (tp->measurePerf) elt->startProcessingTime=rdtsc_oai();
 
@@ -82,14 +91,15 @@ void *one_thread(void *arg) {
       // Check if the job is still alive, else it has been aborted
       mutexlock(tp->incomingFifo.lockF);
 
-      if (myThread->abortFlag)
+      if (myThread->dropJob)
         delNotifiedFIFO_elt(elt);
       else
         pushNotifiedFIFO(elt->reponseFifo, elt);
       myThread->runningOnKey=-1;
       mutexunlock(tp->incomingFifo.lockF);
     }
-  } while (true);
+  } while (!myThread->terminate);
+  return NULL;
 }
 
 void initNamedTpool(char *params,tpool_t *pool, bool performanceMeas, char *name) {
@@ -113,7 +123,6 @@ void initNamedTpool(char *params,tpool_t *pool, bool performanceMeas, char *name
   char *saveptr, * curptr;
   char *parms_cpy=strdup(params);
   pool->nbThreads=0;
-  pool->restrictRNTI=false;
   curptr=strtok_r(parms_cpy,",",&saveptr);
   struct one_thread * ptr;
   char *tname = (name == NULL ? "Tpool" : name);
@@ -121,10 +130,6 @@ void initNamedTpool(char *params,tpool_t *pool, bool performanceMeas, char *name
     int c=toupper(curptr[0]);
 
     switch (c) {
-      case 'U':
-        pool->restrictRNTI=true;
-        break;
-
       case 'N':
         pool->activated=false;
         break;
@@ -137,6 +142,8 @@ void initNamedTpool(char *params,tpool_t *pool, bool performanceMeas, char *name
         pool->allthreads->coreID=atoi(curptr);
         pool->allthreads->id=pool->nbThreads;
         pool->allthreads->pool=pool;
+        pool->allthreads->dropJob = false;
+        pool->allthreads->terminate = false;
         //Configure the thread scheduler policy for Linux
         // set the thread name for debugging
         sprintf(pool->allthreads->name,"%s%d_%d",tname,pool->nbThreads,pool->allthreads->coreID);
@@ -191,12 +198,12 @@ int main() {
   tmp=pullNotifiedFIFO(&myFifo);
   printf("pulled: %lu\n", tmp->key);
   displayList(&myFifo);
-  abortNotifiedFIFO(&myFifo,1005);
+  abortNotifiedFIFOJob(&myFifo,1005);
   printf("aborted 1005\n");
   displayList(&myFifo);
   pushNotifiedFIFO(&myFifo,newNotifiedFIFO_elt(sizeof(struct testData), 12345678, NULL, NULL));
   displayList(&myFifo);
-  abortNotifiedFIFO(&myFifo,12345678);
+  abortNotifiedFIFOJob(&myFifo,12345678);
   printf("aborted 12345678\n");
   displayList(&myFifo);
 
@@ -265,7 +272,7 @@ int main() {
     } else
       printf("Empty list \n");
 
-    abortTpool(&pool,510);
+    abortTpoolJob(&pool,510);
   } while(tmp);
 	*/
   return 0;
diff --git a/common/utils/threadPool/thread-pool.h b/common/utils/threadPool/thread-pool.h
index e448117385a932318358d578e83499c5632853cf..0e4630117d0c7d620b002394664aae69dd879ed8 100644
--- a/common/utils/threadPool/thread-pool.h
+++ b/common/utils/threadPool/thread-pool.h
@@ -72,6 +72,7 @@ typedef struct notifiedFIFO_s {
   notifiedFIFO_elt_t *inF;
   pthread_mutex_t lockF;
   pthread_cond_t  notifF;
+  bool abortFIFO; // if set, the FIFO always returns NULL -> abort condition
 } notifiedFIFO_t;
 
 // You can use this allocator or use any piece of memory
@@ -107,6 +108,7 @@ static inline void delNotifiedFIFO_elt(notifiedFIFO_elt_t *elt) {
 static inline void initNotifiedFIFO_nothreadSafe(notifiedFIFO_t *nf) {
   nf->inF=NULL;
   nf->outF=NULL;
+  nf->abortFIFO = false;
 }
 static inline void initNotifiedFIFO(notifiedFIFO_t *nf) {
   mutexinit(nf->lockF);
@@ -129,14 +131,18 @@ static inline void pushNotifiedFIFO_nothreadSafe(notifiedFIFO_t *nf, notifiedFIF
 
 static inline void pushNotifiedFIFO(notifiedFIFO_t *nf, notifiedFIFO_elt_t *msg) {
   mutexlock(nf->lockF);
-  pushNotifiedFIFO_nothreadSafe(nf,msg);
-  condsignal(nf->notifF);
+  if (!nf->abortFIFO) {
+    pushNotifiedFIFO_nothreadSafe(nf,msg);
+    condsignal(nf->notifF);
+  }
   mutexunlock(nf->lockF);
 }
 
 static inline  notifiedFIFO_elt_t *pullNotifiedFIFO_nothreadSafe(notifiedFIFO_t *nf) {
   if (nf->outF == NULL)
     return NULL;
+  if (nf->abortFIFO)
+    return NULL;
 
   notifiedFIFO_elt_t *ret=nf->outF;
 
@@ -152,9 +158,9 @@ static inline  notifiedFIFO_elt_t *pullNotifiedFIFO_nothreadSafe(notifiedFIFO_t
 
 static inline  notifiedFIFO_elt_t *pullNotifiedFIFO(notifiedFIFO_t *nf) {
   mutexlock(nf->lockF);
-  notifiedFIFO_elt_t *ret;
+  notifiedFIFO_elt_t *ret = NULL;
 
-  while((ret=pullNotifiedFIFO_nothreadSafe(nf)) == NULL)
+  while((ret=pullNotifiedFIFO_nothreadSafe(nf)) == NULL && !nf->abortFIFO)
     condwait(nf->notifF, nf->lockF);
 
   mutexunlock(nf->lockF);
@@ -167,6 +173,11 @@ static inline  notifiedFIFO_elt_t *pollNotifiedFIFO(notifiedFIFO_t *nf) {
   if (tmp != 0 )
     return NULL;
 
+  if (nf->abortFIFO) {
+    mutexunlock(nf->lockF);
+    return NULL;
+  }
+
   notifiedFIFO_elt_t *ret=pullNotifiedFIFO_nothreadSafe(nf);
   mutexunlock(nf->lockF);
   return ret;
@@ -189,7 +200,7 @@ static inline time_stats_t exec_time_stats_NotifiedFIFO(const notifiedFIFO_elt_t
 // This function aborts all messages matching the key
 // If the queue is used in thread pools, it doesn't cancels already running processing
 // because the message has already been picked
-static inline int abortNotifiedFIFO(notifiedFIFO_t *nf, uint64_t key) {
+static inline int abortNotifiedFIFOJob(notifiedFIFO_t *nf, uint64_t key) {
   mutexlock(nf->lockF);
   int nbDeleted=0;
   notifiedFIFO_elt_t **start=&nf->outF;
@@ -211,26 +222,44 @@ static inline int abortNotifiedFIFO(notifiedFIFO_t *nf, uint64_t key) {
   return nbDeleted;
 }
 
+// This functions aborts all messages in the queue, and marks the queue as
+// "aborted", such that every call to it will return NULL
+static inline void abortNotifiedFIFO(notifiedFIFO_t *nf) {
+  mutexlock(nf->lockF);
+  nf->abortFIFO = true;
+  notifiedFIFO_elt_t **elt = &nf->outF;
+  while(*elt != NULL) {
+    notifiedFIFO_elt_t *p = *elt;
+    *elt = (*elt)->next;
+    delNotifiedFIFO_elt(p);
+  }
+
+  if (nf->outF == NULL)
+    nf->inF = NULL;
+  mutexunlock(nf->lockF);
+  condsignal(nf->notifF);
+}
+
 struct one_thread {
   pthread_t  threadID;
   int id;
   int coreID;
   char name[256];
   uint64_t runningOnKey;
-  bool abortFlag;
+  bool dropJob;
+  bool terminate;
   struct thread_pool *pool;
   struct one_thread *next;
 };
 
 typedef struct thread_pool {
-  int activated;
+  bool activated;
   bool measurePerf;
   int traceFd;
   int dummyTraceFd;
   uint64_t cpuCyclesMicroSec;
   uint64_t startProcessingUE;
   int nbThreads;
-  bool restrictRNTI;
   notifiedFIFO_t incomingFifo;
   struct one_thread *allthreads;
 } tpool_t;
@@ -256,6 +285,8 @@ static inline void pushTpool(tpool_t *t, notifiedFIFO_elt_t *msg) {
 
 static inline notifiedFIFO_elt_t *pullTpool(notifiedFIFO_t *responseFifo, tpool_t *t) {
   notifiedFIFO_elt_t *msg= pullNotifiedFIFO(responseFifo);
+  if (msg == NULL)
+    return NULL;
   AssertFatal(t->traceFd, "Thread pool used while not initialized");
   if (t->measurePerf)
     msg->returnTime=rdtsc_oai();
@@ -281,9 +312,10 @@ static inline notifiedFIFO_elt_t *tryPullTpool(notifiedFIFO_t *responseFifo, tpo
   return msg;
 }
 
-static inline int abortTpool(tpool_t *t, uint64_t key) {
+static inline int abortTpoolJob(tpool_t *t, uint64_t key) {
   int nbRemoved=0;
   notifiedFIFO_t *nf=&t->incomingFifo;
+
   mutexlock(nf->lockF);
   notifiedFIFO_elt_t **start=&nf->outF;
 
@@ -300,20 +332,62 @@ static inline int abortTpool(tpool_t *t, uint64_t key) {
   if (t->incomingFifo.outF==NULL)
     t->incomingFifo.inF=NULL;
 
-  struct one_thread *ptr=t->allthreads;
-
-  while(ptr!=NULL) {
-    if (ptr->runningOnKey==key) {
-      ptr->abortFlag=true;
+  struct one_thread *thread = t->allthreads;
+  while (thread != NULL) {
+    if (thread->runningOnKey == key) {
+      thread->dropJob = true;
       nbRemoved++;
     }
 
-    ptr=ptr->next;
+    thread = thread->next;
   }
 
   mutexunlock(nf->lockF);
   return nbRemoved;
 }
+static inline int abortTpool(tpool_t *t) {
+  int nbRemoved=0;
+  /* disables threading: if a message comes in now, we cannot have a race below
+   * as each thread will simply execute the message itself */
+  t->activated = false;
+  notifiedFIFO_t *nf=&t->incomingFifo;
+  mutexlock(nf->lockF);
+  nf->abortFIFO = true;
+  notifiedFIFO_elt_t **start=&nf->outF;
+
+  /* mark threads to abort them */
+  struct one_thread *thread = t->allthreads;
+  while (thread != NULL) {
+    thread->dropJob = true;
+    thread->terminate = true;
+    nbRemoved++;
+    thread = thread->next;
+  }
+
+  /* clear FIFOs */
+  while(*start!=NULL) {
+    notifiedFIFO_elt_t **request=start;
+    *start=(*start)->next;
+    delNotifiedFIFO_elt(*request);
+    *request = NULL;
+    nbRemoved++;
+  }
+
+  if (t->incomingFifo.outF==NULL)
+    t->incomingFifo.inF=NULL;
+
+  condbroadcast(t->incomingFifo.notifF);
+  mutexunlock(nf->lockF);
+
+  /* join threads that are still runing */
+  thread = t->allthreads;
+  while (thread != NULL) {
+    pthread_cancel(thread->threadID);
+    thread = thread->next;
+  }
+
+  return nbRemoved;
+}
 void initNamedTpool(char *params,tpool_t *pool, bool performanceMeas, char *name);
 #define  initTpool(PARAMPTR,TPOOLPTR, MEASURFLAG) initNamedTpool(PARAMPTR,TPOOLPTR, MEASURFLAG, NULL)
 #endif
diff --git a/common/utils/threadPool/thread-pool.md b/common/utils/threadPool/thread-pool.md
index f2422d98225c5d5e4ea9d2f4a84dbcfa6e93ccb1..249951666cb32d413b5c18c28a28645dda652f13 100644
--- a/common/utils/threadPool/thread-pool.md
+++ b/common/utils/threadPool/thread-pool.md
@@ -1,217 +1,219 @@
 # Thread pool
 
-The thread pool is a working server, made of a set of worker threads that can be mapped on CPU cores.
+The **thread pool** is a working server, made of a set of worker threads that can be mapped on CPU cores.  Each thread pool has an **input queue** ("**FIFO**"), from which its workers pick **jobs** (FIFO element) to execute.  When a job is done, the worker sends a message to an output queue, if it has been defined.
 
-Each worker loop on pick from the same input queue jobs to do.
+A selective abort allows to cancel parallel jobs. This can be useful, e.g., if a client pushed jobs, but from a response of one job, the other linked jobs become useless.
 
-When a job is done, the worker sends a return if a return is defined.
-
-A selective abort allows to cancel parallel jobs (usage: a client pushed jobs, but from a response of one job, the other linked jobs becomes useless).
-
-All the thread pool functions are thread safe, nevertheless the working functions are implemented by the thread pool client, so the client has to tackle the parallel execution of his functions called "processingFunc" hereafter.
+All the thread pool functions are thread safe. The functions executed by worker threads are provided by the thread pool client, so the client has to handle the concurrency/parallel execution of his functions.
 
 ## license
     Author:
       Laurent Thomas, Open cells project
       The owner share this piece code to Openairsoftware alliance as per OSA license terms
+      With contributions from Robert Schmidt, OSA
 
-# jobs
+# Jobs
 
-A job is a message (notifiedFIFO_elt_t):  
+A job is a message of type `notifiedFIFO_elt_t`:
 
-* next:
-  internal FIFO chain, do not set it
-* key:
-  a long int that the client can use to identify a message or a group of messages
-* ResponseFifo:
-  if the client defines a response FIFO, the message will be posted back after processing
-* processingFunc:
-  any funtion (type void processingFunc(void *)) that the worker will launch
-* msgData:
-  the data passed to processingFunc. It can be added automatically, or you can set it to a buffer you are managing
-* malloced:
-  a boolean that enable internal free in these cases:
-  no return Fifo or Abort feature
+* `next`: internal FIFO chain, do not set it
+* `key`: a `long int` that the client can use to identify a job or a group of messages
+* `ResponseFifo`: if the client defines a response FIFO, the job will be posted back after processing
+* `processingFunc`: any function (of type `void processingFunc(void *)`) that a worker will process
+* `msgData`: the data passed to `processingFunc`. It can be added automatically, or you can set it to a buffer you are managing
+* `malloced`: a boolean that enables internal free in the case of no return FIFO or abort feature
 
-The job messages can be created with newNotifiedFIFO_elt() and delNotifiedFIFO_elt() or managed by the client.
+The job messages can be created and destroyed with `newNotifiedFIFO_elt()` and `delNotifiedFIFO_elt()`, or managed by the client:
 
-# Queues of jobs
+* `newNotifiedFIFO_elt()`: Creates a job, that will later be used in queues/FIFO
+* `delNotifiedFIFO_elt()`: Deletes a job
+* `NotifiedFifoData()`: gives a pointer to the beginning of free usage memory in a job (you can put any data there, up to 'size' parameter you passed to `newNotifiedFIFO_elt()`)
 
-Queues are type of:
+These 3 calls are not mandatory, you can also use your own memory to save the `malloc()`/`free()` that are behind these calls.
 
-* notifiedFIFO_t that must be initialized by init_notifiedFIFO()
-* No delete function is required, the creator has only to free the data of type notifiedFIFO_t
-* push_notifiedFIFO() add a job in the queue
-* pull_notifiedFIFO() is blocking, poll_notifiedFIFO() is non blocking
-* abort_notifiedFIFO() allows the customer to delete all waiting jobs that match with the key (see key in jobs definition)
+## API details
 
-These queues details hereafter
+### `notifiedFIFO_elt_t *newNotifiedFIFO_elt(int size, uint64_t key, notifiedFIFO_t *reponseFifo, void (*processingFunc)(void *))`
 
-## Common
+Creates a new job. The data part of the job will have size `size`, the job can be identified via `key`. `reponseFifo` is a FIFO queue to which the job should be pushed after being processed by a thread pool (see below for details) using function `processingFunc()`.
 
-newNotifiedFIFO_elt()
+The function returns a pointer to an allocated job.
 
-creates a message, that will later be used in queues/FIFO
+### `void delNotifiedFIFO_elt(notifiedFIFO_elt_t *elt)`
 
-delNotifiedFIFO_elt()
+Free the memory allocated for a job `elt`.
 
-deletes it
+### `void *NotifiedFifoData(notifiedFIFO_elt_t *elt)`
 
-NotifiedFifoData()
+Returns a pointer (`void *`) to the data segment of an allocated job.
 
-gives a pointer to the beginning of free usage memory in a message (you can put any data there, up to 'size' parameter you passed to newNotifiedFIFO_elt()
+# Queues of jobs (`FIFO`)
 
-These 3 calls are not mandatory, you can also use your own memory to save the malloc()/free() that are behind these calls
-iFirst level: non thread safe FIFO (or queues)
+Queues can be used to enqueue messages/jobs, of type `notifiedFIFO_t`.
 
-# low level: fast and simple
+* `initNotifiedFIFO()`: Create a queue
+* No delete function is required, the creator has only to free the data of type `notifiedFIFO_t`
+* `pushNotifiedFIFO()`: Add a job to a queue
+* `pullNotifiedFIFO()`: Pull a job from a queue. This call is blocking until a job arrived.
+* `pollNotifiedFIFO()`: Pull a job from a queue. This call is not blocking, so it returns always very shortly
+* `abortNotifiedFIFOJob()`: Allows to delete all waiting jobs that match a key (see `key` in jobs definition)
+* `abortNotifiedFIFO()`: Aborts a FIFO, such that it will always return `NULL`
 
-initNotifiedFIFO_nothreadSafe()
+Note that in 99.9% of cases, `pull()` is better than `poll()`.
 
-to create a queue
+The above push/pull/poll functions are built on not-thread-safe versions of these functions that are described below.
 
-pushNotifiedFIFO_nothreadSafe()
+## Low level: fast and simple, but not thread-safe
 
-Add a element in a queue
+* `initNotifiedFIFO_nothreadSafe()`: Create a queue
+* `pushNotifiedFIFO_nothreadSafe()`: Add a element in a queue
+* `pullNotifiedFIFO_nothreadSafe()`: Pull a element from a queue
 
-pullNotifiedFIFO_nothreadSafe()
+As these queues are not thread safe, there is NO blocking mechanism, neither `pull()` versus `poll()` calls
 
-pull a element from a queue
+There is no delete for a message queue: you only have to abandon the memory you allocated to call `initNotifiedFIFO_nothreadSafe(notifiedFIFO_t *nf)`
 
-As these queues are not thread safe, there is NO blocking mechanism, neither pull() versus poll() calls
+If you malloced the memory under `nf` parameter, you have to free it.
 
-There is no delete for a message queue: you only have to abandon the memory you allocated to call iinitNotifiedFIFO_nothreadSafe(notifiedFIFO_t *nf)
+## FIFO queue abort
 
-So if you malloced the memory under 'nf' parameter you have to free it, if it is automatic variable (local variable) or global variable, nothing is to be done.
+It is possible to *abort* the FIFO queue. In this case, any pulling/polling of jobs in the queue will return `NULL`. This can be used when stopping a program, to release multiple queues that might depend on each other (a proper design would try to avoid such dependencies, though).
+
+## API details
 
+### `void initNotifiedFIFO(notifiedFIFO_t *nf)`
+### `void initNotifiedFIFO_nothreadSafe(notifiedFIFO_t *nf)`
 
-## thread safe queues
+Initializes a FIFO queue pointed to by `nf` ("notified FIFO").
 
-These queues are built on not thread safe queues when we need thread to thread protection
+### `void pushNotifiedFIFO(notifiedFIFO_t *nf, notifiedFIFO_elt_t *msg)`
+### `void pushNotifiedFIFO_nothreadSafe(notifiedFIFO_t *nf, notifiedFIFO_elt_t *msg)`
 
-initNotifiedFIFO()
+Pushes a job `msg` to the FIFO queue `nf`.
 
-to create a queue
+### `notifiedFIFO_elt_t *pullNotifiedFIFO(notifiedFIFO_t *nf)`
 
-pushNotifiedFIFO()
+Pulls a job from a FIFO queue `nf`. The returned pointer points to the job.  This call is blocking, i.e., a caller will be blocked until there is a message.
 
-Add a element in a queue
+If the FIFO has been aborted, the returned pointer will be `NULL`. In other words, if the call to this functions returns `NULL`, the FIFO has been deactivated.
 
-pullNotifiedFIFO()
+### `notifiedFIFO_elt_t *pollNotifiedFIFO(notifiedFIFO_t *nf)`
+### `notifiedFIFO_elt_t *pullNotifiedFIFO_nothreadSafe(notifiedFIFO_t *nf)`
 
-pull a element from a queue, this call is blocking until a message arrived
+Like `pullNotifiedFIFO()`, but non-blocking: they check if the queue `nf` contains a job, and return it. Otherwise, they return `NULL`.  Note the slight difference in naming: the thread-safe function is called *poll*, not *pull*.
 
-pollNotifiedFIFO()
+Note that unlike for `pullNotifiedFIFO()`, returning `NULL` does not inform whether the queue has been aborted; the caller should manually check the `abortFIFO` flag of `nf` in this case.
 
-pull a element from a queue, this call is not blocking, so it returns always very shortly
+### `int abortNotifiedFIFOJob(notifiedFIFO_t *nf, uint64_t key)`
 
-in 99.9% cases, pull() is better than poll()
+Aborts all jobs in FIFO queue `nf` with key `key`. Jobs already under execution will be silently dropped and not put in the FIFO return queue, if any.
 
-No delete() call, same principle as not thread safe queues
+Returns the number of aborted jobs.
+
+### `void abortNotifiedFIFO(notifiedFIFO_t *nf)`
+
+Aborts the entire FIFO queue `nf`: all jobs will be dropped, and the FIFO is marked as aborted, such that a call to `pullNotifiedFIFO()` returns `NULL`.
+
+Returns the total number of jobs that were waiting or under execution.
 
 # Thread pools
 
-## initialization
-The clients can create one or more thread pools with `initTpool(char *params,tpool_t *pool, bool performanceMeas  )` or `initNamedTpool(char *params,tpool_t *pool, bool performanceMeas , char *name)`
+## Initialization
+
+The clients can create one or more thread pools with
 
-the params string structure: describes a list of cores, separated by "," that run a worker thread
+* `initTpool(char *params, tpool_t *pool, bool performanceMeas)` or
+* `initNamedTpool(char *params,tpool_t *pool, bool performanceMeas , char *name)`
 
-If the core exists on the CPU, the thread pool initialization sets the affinity between this thread and the related code (use negative values is allowed, so the thread will never be mapped on a specific core).
+The threads are governed by the Linux real time scheduler, and their name is set automatically to `Tpool<thread index>_<core id>` if `initTpool()` is used or to `<name><thread index>_<core id>` when `initNamedTpool()` is used.
 
-The threads are all Linux real time scheduler, their name is set automatically to `Tpool<thread index>_<core id>` if initTpool is used or to `<name><thread index>_<core id>` when initNamedTpool is used.
+## Adding jobs
 
-## adding jobs
-The client create their jobs messages as a notifiedFIFO_elt_t, then they push it with pushTpool() (that internally calls push_notifiedFIFO())
+The client create their jobs messages as a `notifiedFIFO_elt_t`. They then push it with `pushTpool()` which internally calls `push_notifiedFIFO()`.
 
-If they need a return, they have to create response queues with init_notifiedFIFO() and set this FIFO pointer in the notifiedFIFO_elt_t before pushing the job.
+If they need a return value (e.g., result of a computation), they have to create response queues with `init_notifiedFIFO()` and set this FIFO pointer in the `notifiedFIFO_elt_t` before pushing the job. This way, multiple result queues can be used in one thread pool.
 
-## abort
+## Abort
 
-A abort service abortTpool() allows to abort all jobs that match a key (see jobs "key"). When the abort returns, it garanties no job (matching the key) response will be posted on response queues.
+A abort service `abortTpoolJob()` allows to abort all jobs that match a key (see a job's `key`). When the abort returns, it garanties that no job (matching the key) response will be posted on response queues.
 
-Nevertheless, jobs already performed before the return of abortTpool() are pushed in the response Fifo queue.
+Nevertheless, jobs already performed before the return of `abortTpoolJob()` are pushed in the response Fifo queue.
+
+`abortTpool()` kills all jobs in the Tpool, and terminates the pool.
 
 ## API details
+
 Each thread pool (there can be several in the same process) should be initialized once using one of the two API's:
 
-### initNamedTpool(char *params,tpool_t *pool, bool performanceMeas,char *name)
+### `void initNamedTpool(char *params, tpool_t *pool, bool performanceMeas, char *name)`
 
-### initTpool(char *params,tpool_t *pool, bool performanceMeas)
+### `void initTpool(char *params, tpool_t *pool, bool performanceMeas)`
 
-`params`: the configuration parameter is a string, elements separator is a comma ",". An element can be:
 
-* N: if a N is in the parameter list, no threads are created
-    The purpose is to keep the same API in any case
-* a CPU with a little number of cores,
-    or in debugging sessions to simplify the human work
-* a number that represent a valid CPU core on the target CPU
-    A thread is created and stick on the core (with set affinity)
-* a number that is not a valid CPU core
-    a floating thread is created (Linux is responsible of the real time core allocation)
+The `params` string describes a list of cores, separated by "," that run a worker thread.  If the core exists on the CPU, the thread pool initialization sets the affinity between this thread and the related core. The following options are available:
 
-example: "-1,-1,-1"
-as there is no core number -1, the thread pool is made of 3 floating threads
-be careful with fix allocation: it is hard to be more clever than Linux kernel
+* `N`: if an `N` is in the parameter list, no threads are created, and the threadpool will be disabled (jobs will be treated by each calling thread independently).
+* A number that represent a valid CPU core on the target CPU, i.e., if there are `M` cores, a number within `[0,M-1]`: A thread is created and pinned to the core (with set affinity)
+* `-1`: The thread will not be mapped onto a specific core (a "floating" thread)
 
-`pool` is the memory you allocated before to store the thread pool internal state (same concept as above queues)
+Example: `-1,-1,-1`: the thread pool is made of 3 floating threads.
+Be careful with a fixed allocation: it is hard to be more clever than Linux kernel!
 
-`performanceMeas` is a flag to enable measurements (well described in documentation)
+`pool` is a pointer to the thread pool object.
+
+`performanceMeas` is a flag to enable measurements (see also below).
 
 `name` is used to build the thread names. 
 
-### pushTpool(tpool_t *t, notifiedFIFO_elt_t *msg)
+### `void pushTpool(tpool_t *t, notifiedFIFO_elt_t *msg)`
 
-adds a job to do in the thread pool
+Adds a job for processing in the thread pool.
 
-The msg data you can set are:
+The job data you can set are, inside `msg`:
 
-* key:  
-    a value for you that you will find back in the response
-    it is also the key for abortTpool()
-* reponseFifo  
-    if you set it, the message will be sent back on this queue when the job is done
-    if you don't set it, no return is performed, the thread pool frees the message 'msg' when the job is done
-* processingFunc  
-    the function the job will run. the function prototype is void <func>(void *memory)
-    the data part (the pointer returned by NotifiedFifoData(msg)) is passed to the function
-    it is used to send data to the processing function and also to write back results
-    of course, writing back results will lead you to use also a return queue (the parameter reponseFifo)
+* `key`: an arbitrary key to find a job in a response queue, and which can be used to abort jobs using `abortTpoolJob()`.
+* `reponseFifo`: if non-`NULL`, the message will be sent back on this queue when the job is done. If `NULL`, the thread pool automatically frees the job when it is done.
+* `processingFunc`: the function to execute for this job.
 
-### pullTpool() collects job result in a return queue
+The `processingFunc` prototype is `void <func>(void *memory)`. The data part of the job (the pointer returned by `NotifiedFifoData(msg)`) is passed to the function. The job data will be updated by `processingFunc`, and the job will be pushed into return queue (the parameter `reponseFifo`).
 
-you collect results in one result queue: the message you gave to pushTpool(), nevertheless it has been updated by processingFunc()
+### `notifiedFIFO_elt_t *pullTpool(notifiedFIFO_t *responseFifo, tpool_t *t)`
 
-An example of multiple return queues, in eNB: I created one single thread pool (because it depends mainly on CPU hardware), but i use two return queues: one for turbo encode, one for turbo decode.
+Collects a job result in a return queue that has been defined in `msg` when calling `pushTpool()`, and that has been updated by `processingFunc()`. Returns the corresponding job pointer of type `notifiedFIFO_elt_t *`.
 
-### tryPullTpool()
+Multiple return queues might be useful. Consider the following example in the eNB: I created one single thread pool (because it depends mainly on CPU hardware), but i use two return queues: one for turbo encode, one for turbo decode. Thus, when performing Turbo encoding/decoding, jobs are pushed into a single thread pool, but will end up in different result queues, such that DL/UL processing can be separated more easily.
 
-is the same, but not blocking (pollTpool() would have been a better name)
+### `notifiedFIFO_elt_t *tryPullTpool(notifiedFIFO_t *responseFifo, tpool_t *t)`
 
-### abortTpool()
+The same as `pullTpool()` in a non-blocking fashion (an alternative name would have been `pollTpool()`).
 
-Is a facility to cancel work you pushed to a thread pool
+### `int abortTpoolJob(tpool_t *t, uint64_t key)`
 
-I used it once: when eNB performs turbo decode, I push all segments in the thread pool.
+Is a facility to cancel work you pushed to a thread pool: every job with a given `key` will be deleted, and results of jobs with such `key` under execution will be dropped.
 
-But when I get back the decoding results, if one segment can't be decoded, I don't need the results of the other segments of the same UE.
+It returns the number of aborted jobs, including the ones that are currently being executed.
 
-## Performance measurements
+I used it once: when eNB performs turbo decode, I push all segments in the thread pool.  But when I get back the decoding results, if one segment can't be decoded, I don't need the results of the other segments of the same UE.
 
-A performance measurement is integrated:
-the pool will automacillay fill timestamps:
+### `int abortTpool(tpool_t *t)`
+
+Aborts the complete Tpool: cancel every work in the input queue, marks to drop existing jobs in processing, and terminates all worker threads. It is afterwards still possible to call functions such as `pushTpool()`, but each calling thread will execute the job itself.
+
+Returns the total number of jobs that were aborted, i.e., waiting for execution or being executed.
+
+## Performance measurements
 
-* creationTime:
-time the request is push to the pool;
-* startProcessingTime:
-time a worker start to run on the job
-* endProcessingTime:
-time the worker finished the job
-* returnTime:
-time the client reads the result
+A performance measurement is integrated: the pool will automacillay fill timestamps if you set the environement variable `threadPoolMeasurements` to a valid file name.  The following measurements will be written to Linux pipe on a per-job basis:
 
-if you set the environement variable:
-threadPoolMeasurements to a valid file name
-These measurements will be wrote to this Linux pipe.
+* `creationTime`: time the request is push to the pool;
+* `startProcessingTime`: time a worker start to run on the job
+* `endProcessingTime`: time the worker finished the job
+* `returnTime`: time the client reads the result
 
-A tool to read the linux fifo and display it in ascii is provided; compute the binay:
-in cmake building directory: make/ninja measurement_display
+The `measurement_display` tool to read the Linux pipe and display it in ASCII is provided.
+In the cmake build directory, type `make/ninja measurement_display`. Use as
+follows:
+```
+sudo threadPoolMeasurements=tpool.meas ./nr-softmodem ...
+./measurement_display tpool.meas
+```
diff --git a/doc/SW_archi.md b/doc/SW_archi.md
index 0a53bf3793b7158220ce1312076476de90b662a1..01d3c377d4382b510207199e60b9c5c6c9ca9d42 100644
--- a/doc/SW_archi.md
+++ b/doc/SW_archi.md
@@ -101,7 +101,6 @@ raw incoming data is in buffer called "rxdata"
 ## nr_fep_full()
 "front end processing" of uplink signal  
 performs DFT on the signal  
-same function (duplicates): phy_procedures_gNB_common_RX()  
 it computes the buffer rxdataF (for frequency) from rxdata (samples over time)  
 rxdataF is the rxdata in frequency domain, phase aligned
 {: .func3}
diff --git a/executables/main-fs6.c b/executables/main-fs6.c
index fff4c511554cc5a1e90ca8fd95bdd533028417ff..01dfc526e976822ee58c4d72bafa016be929e2e0 100644
--- a/executables/main-fs6.c
+++ b/executables/main-fs6.c
@@ -740,6 +740,8 @@ void pusch_procedures_fromsplit(uint8_t *bufferZone, int bufSize, PHY_VARS_eNB *
 
   while (proc->nbDecode > 0) {
     notifiedFIFO_elt_t *req=pullTpool(proc->respDecode, proc->threadPool);
+    if (req == NULL)
+      break; // Tpool has been stopped
     postDecode(proc, req);
     delNotifiedFIFO_elt(req);
   }
diff --git a/executables/main-ocp.c b/executables/main-ocp.c
index 5d949d5a287a1b10e11e12dc1540c7badca78c7b..3a1ad6ec19dba5ba555dbfbbd0eabb6a19ce0105 100644
--- a/executables/main-ocp.c
+++ b/executables/main-ocp.c
@@ -71,7 +71,7 @@ pthread_cond_t sync_cond;
 pthread_mutex_t sync_mutex;
 int sync_var=-1; //!< protected by mutex \ref sync_mutex.
 int config_sync_var=-1;
-volatile int oai_exit = 0;
+int oai_exit = 0;
 double cpuf;
 THREAD_STRUCT thread_struct;
 
diff --git a/executables/main_ru.c b/executables/main_ru.c
index 93a580560e34122ee7ed258aa1d8b34a8a2a7903..3b1ce1116b9059ac08a6f95c9b2bbf55cb290901 100644
--- a/executables/main_ru.c
+++ b/executables/main_ru.c
@@ -73,7 +73,7 @@ pthread_mutex_t sync_mutex;
 int sync_var=-1; //!< protected by mutex \ref sync_mutex.
 int config_sync_var=-1;
 
-volatile int             oai_exit = 0;
+int oai_exit = 0;
 uint16_t sf_ahead = 4;
 RU_t ru_m;
 
diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c
index bc3dccf4fa8c092024d8a79ba3523cf75f05c9a3..14eefa1e3243e3398490e29c9fa878dbd116fded 100644
--- a/executables/nr-gnb.c
+++ b/executables/nr-gnb.c
@@ -247,21 +247,25 @@ void rx_func(void *param) {
     processingData_L1tx_t *syncMsg;
     // Its a FIFO so it maitains the order in which the MAC fills the messages
     // so no need for checking for right slot
-    res = pullTpool(gNB->L1_tx_filled, gNB->threadPool);
+    res = pullTpool(&gNB->L1_tx_filled, &gNB->threadPool);
+    if (res == NULL)
+      return; // Tpool has been stopped
     syncMsg = (processingData_L1tx_t *)NotifiedFifoData(res);
     syncMsg->gNB = gNB;
     syncMsg->timestamp_tx = info->timestamp_tx;
     res->key = slot_tx;
-    pushTpool(gNB->threadPool, res);
+    pushTpool(&gNB->threadPool, res);
   } else if (get_softmodem_params()->continuous_tx) {
-    notifiedFIFO_elt_t *res = pullTpool(gNB->L1_tx_free, gNB->threadPool);
+    notifiedFIFO_elt_t *res = pullTpool(&gNB->L1_tx_free, &gNB->threadPool);
+    if (res == NULL)
+      return; // Tpool has been stopped
     processingData_L1tx_t *syncMsg = (processingData_L1tx_t *)NotifiedFifoData(res);
     syncMsg->gNB = gNB;
     syncMsg->timestamp_tx = info->timestamp_tx;
     syncMsg->frame = frame_tx;
     syncMsg->slot = slot_tx;
     res->key = slot_tx;
-    pushNotifiedFIFO(gNB->L1_tx_out, res);
+    pushNotifiedFIFO(&gNB->L1_tx_out, res);
   }
 
 #if 0
@@ -376,7 +380,8 @@ void *tx_reorder_thread(void* param) {
     notifiedFIFO_elt_t *resL1Reserve = NULL;
   
 
-  resL1Reserve=pullTpool(gNB->L1_tx_out, gNB->threadPool);
+  resL1Reserve = pullTpool(&gNB->L1_tx_out, &gNB->threadPool);
+  AssertFatal(resL1Reserve != NULL, "pullTpool() did not return start message in %s\n", __func__);
   int next_tx_slot=((processingData_L1tx_t *)NotifiedFifoData(resL1Reserve))->slot;
   
   while (!oai_exit) {
@@ -385,20 +390,22 @@ void *tx_reorder_thread(void* param) {
        resL1=resL1Reserve;
        if (((processingData_L1tx_t *)NotifiedFifoData(resL1))->slot != next_tx_slot) {
          LOG_E(PHY,"order mistake\n");
-	 resL1Reserve=NULL;
-	 resL1 = pullTpool(gNB->L1_tx_out, gNB->threadPool);
+         resL1Reserve = NULL;
+         resL1 = pullTpool(&gNB->L1_tx_out, &gNB->threadPool);
        }
      } else { 
-       resL1 = pullTpool(gNB->L1_tx_out, gNB->threadPool);
-       if (((processingData_L1tx_t *)NotifiedFifoData(resL1))->slot != next_tx_slot) {
+       resL1 = pullTpool(&gNB->L1_tx_out, &gNB->threadPool);
+       if (resL1 != NULL && ((processingData_L1tx_t *)NotifiedFifoData(resL1))->slot != next_tx_slot) {
           if (resL1Reserve)
               LOG_E(PHY,"error, have a stored packet, then a second one\n");
-	  resL1Reserve=resL1;
-          resL1 = pullTpool(gNB->L1_tx_out, gNB->threadPool);
+          resL1Reserve = resL1;
+          resL1 = pullTpool(&gNB->L1_tx_out, &gNB->threadPool);
           if (((processingData_L1tx_t *)NotifiedFifoData(resL1))->slot != next_tx_slot)
-	    LOG_E(PHY,"error, pull two msg, none is good\n");
+            LOG_E(PHY,"error, pull two msg, none is good\n");
        }
     }
+    if (resL1 == NULL)
+      break; // Tpool has been stopped
     processingData_L1tx_t *syncMsgL1= (processingData_L1tx_t *)NotifiedFifoData(resL1);
     processingData_RU_t syncMsgRU;
     syncMsgRU.frame_tx = syncMsgL1->frame;
@@ -410,7 +417,7 @@ void *tx_reorder_thread(void* param) {
       next_tx_slot = (syncMsgRU.slot_tx + 1) % slots_per_frame;
     } else
       next_tx_slot = get_next_downlink_slot(gNB, &gNB->gNB_config, syncMsgRU.frame_tx, syncMsgRU.slot_tx);
-    pushNotifiedFIFO(gNB->L1_tx_free, resL1);
+    pushNotifiedFIFO(&gNB->L1_tx_free, resL1);
     if (resL1==resL1Reserve)
        resL1Reserve=NULL;
     ru_tx_func((void*)&syncMsgRU);
@@ -423,8 +430,6 @@ void init_gNB_Tpool(int inst) {
   gNB = RC.gNB[inst];
   gNB_L1_proc_t *proc = &gNB->proc;
 
-  // ULSCH decoding threadpool
-  gNB->threadPool = (tpool_t*)malloc(sizeof(tpool_t));
   int numCPU = sysconf(_SC_NPROCESSORS_ONLN);
   LOG_I(PHY,"Number of threads requested in config file: %d, Number of threads available on this machine: %d\n",gNB->thread_pool_size,numCPU);
   int threadCnt = min(numCPU, gNB->thread_pool_size);
@@ -437,33 +442,28 @@ void init_gNB_Tpool(int inst) {
     s_offset += 3;
   }
   if (getenv("noThreads")) strcpy(pool, "n");
-  initTpool(pool, gNB->threadPool, cpumeas(CPUMEAS_GETSTATE));
+  initTpool(pool, &gNB->threadPool, cpumeas(CPUMEAS_GETSTATE));
   // ULSCH decoder result FIFO
-  gNB->respDecode = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  initNotifiedFIFO(gNB->respDecode);
+  initNotifiedFIFO(&gNB->respDecode);
 
   // L1 RX result FIFO 
-  gNB->resp_L1 = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  initNotifiedFIFO(gNB->resp_L1);
-  notifiedFIFO_elt_t *msg = newNotifiedFIFO_elt(sizeof(processingData_L1_t),0,gNB->resp_L1,rx_func);
-  pushNotifiedFIFO(gNB->resp_L1,msg); // to unblock the process in the beginning
+  initNotifiedFIFO(&gNB->resp_L1);
+  notifiedFIFO_elt_t *msg = newNotifiedFIFO_elt(sizeof(processingData_L1_t), 0, &gNB->resp_L1, rx_func);
+  pushNotifiedFIFO(&gNB->resp_L1, msg); // to unblock the process in the beginning
 
   // L1 TX result FIFO 
-  gNB->L1_tx_free = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  gNB->L1_tx_filled = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  gNB->L1_tx_out = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  initNotifiedFIFO(gNB->L1_tx_free);
-  initNotifiedFIFO(gNB->L1_tx_filled);
-  initNotifiedFIFO(gNB->L1_tx_out);
+  initNotifiedFIFO(&gNB->L1_tx_free);
+  initNotifiedFIFO(&gNB->L1_tx_filled);
+  initNotifiedFIFO(&gNB->L1_tx_out);
   
   // we create 2 threads for L1 tx processing
   for (int i=0; i < 2; i++) {
-    notifiedFIFO_elt_t *msgL1Tx = newNotifiedFIFO_elt(sizeof(processingData_L1tx_t),0,gNB->L1_tx_out,tx_func);
+    notifiedFIFO_elt_t *msgL1Tx = newNotifiedFIFO_elt(sizeof(processingData_L1tx_t), 0, &gNB->L1_tx_out, tx_func);
     processingData_L1tx_t *msgDataTx = (processingData_L1tx_t *)NotifiedFifoData(msgL1Tx);
-    memset(msgDataTx,0, sizeof(processingData_L1tx_t));
+    memset(msgDataTx, 0, sizeof(processingData_L1tx_t));
     init_DLSCH_struct(gNB, msgDataTx);
     memset(msgDataTx->ssb, 0, 64*sizeof(NR_gNB_SSB_t));
-    pushNotifiedFIFO(gNB->L1_tx_free,msgL1Tx); // to unblock the process in the beginning
+    pushNotifiedFIFO(&gNB->L1_tx_free, msgL1Tx); // to unblock the process in the beginning
   }
 
   if ((!get_softmodem_params()->emulate_l1) && (!IS_SOFTMODEM_NOSTATS_BIT))
@@ -474,6 +474,21 @@ void init_gNB_Tpool(int inst) {
 }
 
 
+void term_gNB_Tpool(int inst) {
+  PHY_VARS_gNB *gNB = RC.gNB[inst];
+  abortTpool(&gNB->threadPool);
+  abortNotifiedFIFO(&gNB->respDecode);
+  abortNotifiedFIFO(&gNB->resp_L1);
+  abortNotifiedFIFO(&gNB->L1_tx_free);
+  abortNotifiedFIFO(&gNB->L1_tx_filled);
+  abortNotifiedFIFO(&gNB->L1_tx_out);
+
+  gNB_L1_proc_t *proc = &gNB->proc;
+  if (!get_softmodem_params()->emulate_l1)
+    pthread_join(proc->L1_stats_thread, NULL);
+  pthread_join(proc->pthread_tx_reorder, NULL);
+}
+
 /*!
  * \brief Terminate gNB TX and RX threads.
  */
@@ -540,15 +555,9 @@ void init_eNB_afterRU(void) {
 		  gNB->RU_list[ru_id]->idx);
       
       for (i=0; i<gNB->RU_list[ru_id]->nb_rx; aa++,i++) {
-	LOG_I(PHY,"Attaching RU %d antenna %d to gNB antenna %d\n",gNB->RU_list[ru_id]->idx,i,aa);
-	gNB->prach_vars.rxsigF[aa]    =  gNB->RU_list[ru_id]->prach_rxsigF[0][i];
-#if 0
-printf("before %p\n", gNB->common_vars.rxdataF[aa]);
-#endif
-	gNB->common_vars.rxdataF[aa]     =  gNB->RU_list[ru_id]->common.rxdataF[i];
-#if 0
-printf("after %p\n", gNB->common_vars.rxdataF[aa]);
-#endif
+        LOG_I(PHY,"Attaching RU %d antenna %d to gNB antenna %d\n",gNB->RU_list[ru_id]->idx,i,aa);
+        gNB->prach_vars.rxsigF[aa]    =  gNB->RU_list[ru_id]->prach_rxsigF[0][i];
+        gNB->common_vars.rxdataF[aa]     =  gNB->RU_list[ru_id]->common.rxdataF[i];
       }
     }
 
@@ -616,6 +625,7 @@ void init_gNB(int single_thread_flag,int wait_for_sync) {
 void stop_gNB(int nb_inst) {
   for (int inst=0; inst<nb_inst; inst++) {
     LOG_I(PHY,"Killing gNB %d processing threads\n",inst);
+    term_gNB_Tpool(inst);
     kill_gNB_proc(inst);
   }
 }
diff --git a/executables/nr-ru.c b/executables/nr-ru.c
index 3ddca7419402abf3e9d0570246b03d06ab54205f..baff75731244e0912d76028f8c4a26c213d89a85 100644
--- a/executables/nr-ru.c
+++ b/executables/nr-ru.c
@@ -80,7 +80,7 @@ static int DEFBFW[] = {0x00007fff};
 #include "nfapi_interface.h"
 #include <nfapi/oai_integration/vendor_ext.h>
 
-extern volatile int oai_exit;
+extern int oai_exit;
 
 extern struct timespec timespec_sub(struct timespec lhs, struct timespec rhs);
 extern struct timespec timespec_add(struct timespec lhs, struct timespec rhs);
@@ -1251,7 +1251,9 @@ void *ru_thread( void *param ) {
     }
 
     // At this point, all information for subframe has been received on FH interface
-    res = pullTpool(gNB->resp_L1, gNB->threadPool);
+    res = pullTpool(&gNB->resp_L1, &gNB->threadPool);
+    if (res == NULL)
+      break; // Tpool has been stopped
     syncMsg = (processingData_L1_t *)NotifiedFifoData(res);
     syncMsg->gNB = gNB;
     syncMsg->frame_rx = proc->frame_rx;
@@ -1260,7 +1262,7 @@ void *ru_thread( void *param ) {
     syncMsg->slot_tx = proc->tti_tx;
     syncMsg->timestamp_tx = proc->timestamp_tx;
     res->key = proc->tti_rx;
-    pushTpool(gNB->threadPool, res);
+    pushTpool(&gNB->threadPool, res);
   }
 
   printf( "Exiting ru_thread \n");
@@ -1271,13 +1273,6 @@ void *ru_thread( void *param ) {
     else LOG_I(PHY,"RU %d rf device stopped\n",ru->idx);
   }
 
-  res = pullNotifiedFIFO(gNB->resp_L1);
-  delNotifiedFIFO_elt(res);
-  res = pullNotifiedFIFO(gNB->L1_tx_free);
-  delNotifiedFIFO_elt(res);
-  res = pullNotifiedFIFO(gNB->L1_tx_free);
-  delNotifiedFIFO_elt(res);
-
   ru_thread_status = 0;
   return &ru_thread_status;
 }
@@ -1336,40 +1331,26 @@ void init_RU_proc(RU_t *ru) {
 
 }
 
+
 void kill_NR_RU_proc(int inst) {
   RU_t *ru = RC.ru[inst];
   RU_proc_t *proc = &ru->proc;
-  LOG_D(PHY, "Joining pthread_FH\n");
-  pthread_join(proc->pthread_FH, NULL);
 
-  if (get_nprocs() >= 2) {
-    if (ru->feprx) {
-      pthread_mutex_lock(&proc->mutex_fep);
-      proc->instance_cnt_fep = 0;
-      pthread_mutex_unlock(&proc->mutex_fep);
-      pthread_cond_signal(&proc->cond_fep);
-      LOG_D(PHY, "Joining pthread_fep\n");
-      pthread_join(proc->pthread_fep, NULL);
-      pthread_mutex_destroy(&proc->mutex_fep);
-      pthread_cond_destroy(&proc->cond_fep);
-    }
+  /* Note: it seems pthread_FH and and FEP thread below both use
+   * mutex_fep/cond_fep. Thus, we unlocked above for pthread_FH above and do
+   * the same for FEP thread below again (using broadcast() to ensure both
+   * threads get the signal). This one will also destroy the mutex and cond. */
+  pthread_mutex_lock(&proc->mutex_fep);
+  proc->instance_cnt_fep = 0;
+  pthread_cond_broadcast(&proc->cond_fep);
+  pthread_mutex_unlock( &proc->mutex_fep );
+  pthread_join(proc->pthread_FH, NULL);
 
-    if (ru->feptx_ofdm) {
-      pthread_mutex_lock(&proc->mutex_feptx);
-      proc->instance_cnt_feptx = 0;
-      pthread_mutex_unlock(&proc->mutex_feptx);
-      pthread_cond_signal(&proc->cond_feptx);
-      LOG_D(PHY, "Joining pthread_feptx\n");
-      pthread_join(proc->pthread_feptx, NULL);
-      pthread_mutex_destroy(&proc->mutex_feptx);
-      pthread_cond_destroy(&proc->cond_feptx);
-    }
-  }
+  if (ru->feprx)
+    nr_kill_feprx_thread(ru);
 
-  if (opp_enabled) {
-    LOG_D(PHY, "Joining ru_stats_thread\n");
-    pthread_join(ru->ru_stats_thread, NULL);
-  }
+  if (ru->feptx_ofdm)
+    nr_kill_feptx_thread(ru);
 }
 
 int check_capabilities(RU_t *ru,RRU_capabilities_t *cap) {
diff --git a/executables/nr-softmodem-common.h b/executables/nr-softmodem-common.h
index 1e6c556acffb457ff8646e0c5e44c7f14b44d6a3..e4e4eee8ce6aa946248519bb385ac7e7fe16e110 100644
--- a/executables/nr-softmodem-common.h
+++ b/executables/nr-softmodem-common.h
@@ -138,7 +138,7 @@ extern int rx_input_level_dBm;
 extern uint64_t num_missed_slots; // counter for the number of missed slots
 
 extern int oaisim_flag;
-extern volatile int  oai_exit;
+extern int oai_exit;
 
 extern openair0_config_t openair0_cfg[MAX_CARDS];
 extern pthread_cond_t sync_cond;
diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c
index 54345dc6b022cf4555dd81a49cdbe64c55831dee..5da5fe42f5269b52e9d2b9f558a72f2c0df23906 100644
--- a/executables/nr-softmodem.c
+++ b/executables/nr-softmodem.c
@@ -93,7 +93,7 @@ int sync_var=-1; //!< protected by mutex \ref sync_mutex.
 int config_sync_var=-1;
 
 volatile int             start_gNB = 0;
-volatile int             oai_exit = 0;
+int oai_exit = 0;
 
 static int wait_for_sync = 0;
 
@@ -716,9 +716,6 @@ int main( int argc, char **argv ) {
 
   printf("NFAPI MODE:%s\n", nfapi_mode_str);
 
-  if (NFAPI_MODE==NFAPI_MODE_VNF)
-    wait_nfapi_init("main?");
-
   printf("START MAIN THREADS\n");
   // start the main threads
   number_of_cards = 1;
@@ -787,60 +784,29 @@ int main( int argc, char **argv ) {
     pthread_mutex_unlock(&sync_mutex);
   }
 
-  printf("About to call end_configmodule() from %s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);
-
-  // We have to set PARAMFLAG_NOFREE on right paramters before re-enabling end_configmodule()
-
-  //end_configmodule();
-  printf("Called end_configmodule() from %s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);
   // wait for end of program
-  printf("TYPE <CTRL-C> TO TERMINATE\n");
-  //getchar();
   printf("Entering ITTI signals handler\n");
+  printf("TYPE <CTRL-C> TO TERMINATE\n");
   itti_wait_tasks_end();
   printf("Returned from ITTI signal handler\n");
   oai_exit=1;
   printf("oai_exit=%d\n",oai_exit);
-  // stop threads
-  /*#ifdef XFORMS
 
-      printf("waiting for XFORMS thread\n");
-
-      if (do_forms==1) {
-        pthread_join(forms_thread,&status);
-        fl_hide_form(form_stats->stats_form);
-        fl_free_form(form_stats->stats_form);
-
-          fl_hide_form(form_stats_l2->stats_form);
-          fl_free_form(form_stats_l2->stats_form);
-
-          for(UE_id=0; UE_id<scope_enb_num_ue; UE_id++) {
-      for(CC_id=0; CC_id<MAX_NUM_CCs; CC_id++) {
-        fl_hide_form(form_enb[CC_id][UE_id]->phy_scope_gNB);
-        fl_free_form(form_enb[CC_id][UE_id]->phy_scope_gNB);
-      }
-          }
-      }
-
-  #endif*/
-  printf("stopping MODEM threads\n");
   // cleanup
-  stop_gNB(NB_gNB_INST);
+  if (RC.nb_nr_L1_inst > 0)
+    stop_gNB(RC.nb_nr_L1_inst);
 
-  if (RC.nb_nr_L1_inst > 0) {
-    stop_RU(NB_RU);
-  }
+  if (RC.nb_RU > 0)
+    stop_RU(RC.nb_RU);
 
   /* release memory used by the RU/gNB threads (incomplete), after all
    * threads have been stopped (they partially use the same memory) */
-  for (int inst = 0; inst < NB_gNB_INST; inst++) {
-    //free_transport(RC.gNB[inst]);
-    phy_free_nr_gNB(RC.gNB[inst]);
+  for (int inst = 0; inst < RC.nb_RU; inst++) {
+    nr_phy_free_RU(RC.ru[inst]);
   }
 
-  for (int inst = 0; inst < NB_RU; inst++) {
-    kill_NR_RU_proc(inst);
-    nr_phy_free_RU(RC.ru[inst]);
+  for (int inst = 0; inst < RC.nb_nr_L1_inst; inst++) {
+    phy_free_nr_gNB(RC.gNB[inst]);
   }
 
   pthread_cond_destroy(&sync_cond);
@@ -851,10 +817,7 @@ int main( int argc, char **argv ) {
 
   // *** Handle per CC_id openair0
 
-  for(ru_id=0; ru_id<NB_RU; ru_id++) {
-    if (RC.ru[ru_id]->rfdevice.trx_end_func)
-      RC.ru[ru_id]->rfdevice.trx_end_func(&RC.ru[ru_id]->rfdevice);
-
+  for(ru_id = 0; ru_id < RC.nb_RU; ru_id++) {
     if (RC.ru[ru_id]->ifdevice.trx_end_func)
       RC.ru[ru_id]->ifdevice.trx_end_func(&RC.ru[ru_id]->ifdevice);
   }
diff --git a/executables/nr-ue.c b/executables/nr-ue.c
index 7b39dfdcaa779049ff32161eb9e9d1bc0966bac8..821325f5f14eaf3af772a2d586f3b74a53c013c7 100644
--- a/executables/nr-ue.c
+++ b/executables/nr-ue.c
@@ -692,6 +692,8 @@ void processSlotRX(void *arg) {
     // Wait for PUSCH processing to finish
     notifiedFIFO_elt_t *res;
     res = pullTpool(&rxtxD->txFifo,&(get_nrUE_params()->Tpool));
+    if (res == NULL)
+      return; // Tpool has been stopped
     delNotifiedFIFO_elt(res);
 
     // calling UL_indication to schedule things other than PUSCH (eg, PUCCH)
@@ -860,8 +862,8 @@ void *UE_thread(void *arg) {
 
   while (!oai_exit) {
     if (UE->lost_sync) {
-      int nb = abortTpool(&(get_nrUE_params()->Tpool),RX_JOB_ID);
-      nb += abortNotifiedFIFO(&nf, RX_JOB_ID);
+      int nb = abortTpoolJob(&(get_nrUE_params()->Tpool),RX_JOB_ID);
+      nb += abortNotifiedFIFOJob(&nf, RX_JOB_ID);
       LOG_I(PHY,"Number of aborted slots %d\n",nb);
       for (int i=0; i<nb; i++)
         pushNotifiedFIFO_nothreadSafe(&freeBlocks, newNotifiedFIFO_elt(sizeof(nr_rxtx_thread_data_t), RX_JOB_ID,&nf,processSlotRX));
@@ -1008,6 +1010,8 @@ void *UE_thread(void *arg) {
 
     while (nbSlotProcessing >= NR_RX_NB_TH) {
       res=pullTpool(&nf, &(get_nrUE_params()->Tpool));
+      if (res == NULL)
+        break; // Tpool has been stopped
       nbSlotProcessing--;
       nr_rxtx_thread_data_t *tmp=(nr_rxtx_thread_data_t *)res->msgData;
 
diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c
index de3c1088412f657081a73ea4978ba5ad657e20d0..92dab900149a4679a90b78350114a4462414ab3d 100644
--- a/executables/nr-uesoftmodem.c
+++ b/executables/nr-uesoftmodem.c
@@ -105,7 +105,7 @@ instance_t CUuniqInstance=0;
 instance_t DUuniqInstance=0;
 
 RAN_CONTEXT_t RC;
-volatile int             oai_exit = 0;
+int oai_exit = 0;
 
 
 extern int16_t  nr_dlsch_demod_shift;
diff --git a/executables/softmodem-common.h b/executables/softmodem-common.h
index 591eee9c56c14de37b4cf9f7a28f14aa391adb11..8a898c69b8ff50b9b1e79fbe210492b13dc837fc 100644
--- a/executables/softmodem-common.h
+++ b/executables/softmodem-common.h
@@ -283,7 +283,7 @@ extern int32_t uplink_frequency_offset[MAX_NUM_CCs][4];
 extern int usrp_tx_thread;
 extern uint16_t sl_ahead;
 extern uint16_t sf_ahead;
-extern volatile int  oai_exit;
+extern int oai_exit;
 
 void tx_func(void *param);
 void rx_func(void *param);
diff --git a/nfapi/oai_integration/nfapi_pnf.c b/nfapi/oai_integration/nfapi_pnf.c
index 205f8799645a35583e25ab2d0c6c9f514547fa7a..4f07e79a7833783b1b89b76edaf9524b887ba52a 100644
--- a/nfapi/oai_integration/nfapi_pnf.c
+++ b/nfapi/oai_integration/nfapi_pnf.c
@@ -1103,7 +1103,7 @@ notifiedFIFO_elt_t *l1tx_message_extract(PHY_VARS_gNB *gNB, int frame, int slot)
   notifiedFIFO_elt_t *res;
 
   //TODO: This needs to be reworked for nfapi to work
-  res = pullTpool(gNB->L1_tx_free, gNB->threadPool);
+  res = pullTpool(&gNB->L1_tx_free, &gNB->threadPool);
   return res;
 }
 
@@ -1130,7 +1130,7 @@ int pnf_phy_ul_dci_req(gNB_L1_rxtx_proc_t *proc, nfapi_pnf_p7_config_t *pnf_p7,
     }
   }
 
-  pushNotifiedFIFO(gNB->L1_tx_filled,res);
+  pushNotifiedFIFO(&gNB->L1_tx_filled, res);
 
   return 0;
 }
@@ -1237,7 +1237,7 @@ int pnf_phy_dl_tti_req(gNB_L1_rxtx_proc_t *proc, nfapi_pnf_p7_config_t *pnf_p7,
     else {
       NFAPI_TRACE(NFAPI_TRACE_ERROR, "%s() UNKNOWN:%d\n", __FUNCTION__, dl_tti_pdu_list[i].PDUType);
     }
-    pushNotifiedFIFO(gNB->L1_tx_filled,res);
+    pushNotifiedFIFO(&gNB->L1_tx_filled, res);
   }
 
   if(req->vendor_extension)
diff --git a/nfapi/oai_integration/nfapi_vnf.c b/nfapi/oai_integration/nfapi_vnf.c
index 8cc1bda7828bb3f1dfde92f7e862157906e6c9b8..eabadc619387d4441444145aef78e4e32c3f6a19 100644
--- a/nfapi/oai_integration/nfapi_vnf.c
+++ b/nfapi/oai_integration/nfapi_vnf.c
@@ -1687,7 +1687,6 @@ int nr_config_resp_cb(nfapi_vnf_config_t *config, int p5_idx, nfapi_nr_config_re
   nfapi_nr_start_request_scf_t req;
   NFAPI_TRACE(NFAPI_TRACE_INFO, "[VNF] Received NFAPI_CONFIG_RESP idx:%d phy_id:%d\n", p5_idx, resp->header.phy_id);
   NFAPI_TRACE(NFAPI_TRACE_INFO, "[VNF] Calling oai_enb_init()\n");
-  oai_enb_init(); // TODO: change to gnb
   memset(&req, 0, sizeof(req));
   req.header.message_id = NFAPI_NR_PHY_MSG_TYPE_START_REQUEST;
   req.header.phy_id = resp->header.phy_id;
diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c
index c3baa06da64d3e469e22fafc32611bef59c0beca..6b984f53db81b0eba32c5dbef43d5f202d7534e3 100644
--- a/openair1/PHY/INIT/nr_init.c
+++ b/openair1/PHY/INIT/nr_init.c
@@ -625,9 +625,10 @@ int phy_init_nr_gNB(PHY_VARS_gNB *gNB,
 
   gNB->first_run_I0_measurements = 1;
 
-  common_vars->rxdata  = (int32_t **)malloc16(Prx*sizeof(int32_t*));
   common_vars->txdataF = (int32_t **)malloc16(Ptx*sizeof(int32_t*));
   common_vars->rxdataF = (int32_t **)malloc16(Prx*sizeof(int32_t*));
+  /* Do NOT allocate per-antenna txdataF/rxdataF: the gNB gets a pointer to the
+   * RU to copy/recover freq-domain memory from there */
   common_vars->beam_id = (uint8_t **)malloc16(Ptx*sizeof(uint8_t*));
 
   for (i=0;i<Ptx;i++){
@@ -638,10 +639,6 @@ int phy_init_nr_gNB(PHY_VARS_gNB *gNB,
     common_vars->beam_id[i] = (uint8_t*)malloc16_clear(fp->symbols_per_slot*fp->slots_per_frame*sizeof(uint8_t));
     memset(common_vars->beam_id[i],255,fp->symbols_per_slot*fp->slots_per_frame);
   }
-  for (i=0;i<Prx;i++){
-    common_vars->rxdataF[i] = (int32_t*)malloc16_clear(fp->samples_per_frame_wCP*sizeof(int32_t));
-    common_vars->rxdata[i] = (int32_t*)malloc16_clear(fp->samples_per_frame*sizeof(int32_t));
-  }
   common_vars->debugBuff = (int32_t*)malloc16_clear(fp->samples_per_frame*sizeof(int32_t)*100);	
   common_vars->debugBuff_sample_offset = 0; 
 
@@ -784,13 +781,9 @@ void phy_free_nr_gNB(PHY_VARS_gNB *gNB)
     free_and_zero(common_vars->beam_id[i]);
   }
 
-  for (int i = 0; i < Prx; ++i) {
-    free_and_zero(common_vars->rxdataF[i]);
-    free_and_zero(common_vars->rxdata[i]);
-  }
-
+  /* Do NOT free per-antenna txdataF/rxdataF: the gNB gets a pointer to the
+   * RU's txdataF/rxdataF, and the RU will free that */
   free_and_zero(common_vars->txdataF);
-  free_and_zero(common_vars->rxdata);
   free_and_zero(common_vars->rxdataF);
   free_and_zero(common_vars->beam_id);
 
diff --git a/openair1/PHY/INIT/nr_init_ru.c b/openair1/PHY/INIT/nr_init_ru.c
index 75d78738fc6fa74f9d3e821087ea5479a43654ea..0a357a5255523ea3adb9fc6553b10a1a13353568 100644
--- a/openair1/PHY/INIT/nr_init_ru.c
+++ b/openair1/PHY/INIT/nr_init_ru.c
@@ -176,8 +176,11 @@ void nr_phy_free_RU(RU_t *ru)
   LOG_I(PHY, "Feeing RU signal buffers (if_south %s) nb_tx %d\n", ru_if_types[ru->if_south], ru->nb_tx);
 
   if (ru->if_south <= REMOTE_IF5) { // this means REMOTE_IF5 or LOCAL_RF, so free memory for time-domain signals
-    for (i = 0; i < ru->nb_tx; i++)
-      free_and_zero(ru->common.txdata[i]);
+    // Hack: undo what is done at allocation
+    for (i = 0; i < ru->nb_tx; i++) {
+      int32_t *p = &ru->common.txdata[i][-ru->sf_extension];
+      free_and_zero(p);
+    }
     free_and_zero(ru->common.txdata);
 
     for (i = 0; i < ru->nb_rx; i++)
@@ -199,7 +202,7 @@ void nr_phy_free_RU(RU_t *ru)
 
     // free FFT output buffers (RX)
     for (i = 0; i < ru->nb_rx; i++) free_and_zero(ru->common.rxdataF[i]);
-    free_and_zero(ru->common.rxdataF);
+      free_and_zero(ru->common.rxdataF);
 
     for (j=0;j<NUMBER_OF_NR_RU_PRACH_OCCASIONS_MAX;j++) {
       for (i = 0; i < ru->nb_rx; i++)
diff --git a/openair1/PHY/LTE_TRANSPORT/dlsch_coding.c b/openair1/PHY/LTE_TRANSPORT/dlsch_coding.c
index 13109d8f5a19286d43644e2d30a63fdda4755575..a5aff0974eb8f3f9ecfcb26cf7dfb0a54bcf71c3 100644
--- a/openair1/PHY/LTE_TRANSPORT/dlsch_coding.c
+++ b/openair1/PHY/LTE_TRANSPORT/dlsch_coding.c
@@ -59,7 +59,7 @@
   uint64_t deadline,
   uint64_t period);*/
 
-extern volatile int oai_exit;
+extern int oai_exit;
 
 void free_eNB_dlsch(LTE_eNB_DLSCH_t *dlsch) {
   int i, r, aa, layer;
diff --git a/openair1/PHY/LTE_TRANSPORT/ulsch_decoding.c b/openair1/PHY/LTE_TRANSPORT/ulsch_decoding.c
index fceff580a3499c3ef8af43b413dffd938b4db027..a6dde08a2c158926e53ee58fa5d2ed0c1f36f2bf 100644
--- a/openair1/PHY/LTE_TRANSPORT/ulsch_decoding.c
+++ b/openair1/PHY/LTE_TRANSPORT/ulsch_decoding.c
@@ -44,7 +44,7 @@
 #include <executables/split_headers.h>
 
 extern WORKER_CONF_t get_thread_worker_conf(void);
-extern volatile int oai_exit;
+extern int oai_exit;
 
 
 
diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c
index 8672f3ae29aadf3d4d0bcf8e2d37864a3c448319..af00abddc5323f53a5a4548c4addbff31cfbd425 100644
--- a/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c
+++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c
@@ -425,11 +425,13 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
     encoder_implemparams_t* perJobImpp=(encoder_implemparams_t*)NotifiedFifoData(req);
     *perJobImpp=impp;
     perJobImpp->macro_num=j;
-    pushTpool(gNB->threadPool,req);
+    pushTpool(&gNB->threadPool, req);
     nbJobs++;
   }
   while(nbJobs) {
-    notifiedFIFO_elt_t *req=pullTpool(&nf, gNB->threadPool);
+    notifiedFIFO_elt_t *req=pullTpool(&nf, &gNB->threadPool);
+    if (req == NULL)
+      break; // Tpool has been stopped
     delNotifiedFIFO_elt(req);
     nbJobs--;
 
diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c
index 6f49bcd053e5296e419cb78e32cc747f748421d6..57b0602c7e8397afbd6e2badf3cc93a8341c0803 100644
--- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c
+++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c
@@ -553,7 +553,7 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB,
     E = nr_get_E(G, harq_process->C, Qm, n_layers, r);
 
     union ldpcReqUnion id = {.s={ulsch->rnti,frame,nr_tti_rx,0,0}};
-    notifiedFIFO_elt_t *req=newNotifiedFIFO_elt(sizeof(ldpcDecode_t), id.p, phy_vars_gNB->respDecode, nr_processULSegment_ptr);
+    notifiedFIFO_elt_t *req = newNotifiedFIFO_elt(sizeof(ldpcDecode_t), id.p, &phy_vars_gNB->respDecode, nr_processULSegment_ptr);
     ldpcDecode_t * rdata=(ldpcDecode_t *) NotifiedFifoData(req);
 
     rdata->gNB = phy_vars_gNB;
@@ -574,7 +574,7 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB,
     rdata->ulsch = ulsch;
     rdata->ulsch_id = ULSCH_id;
     rdata->tbslbrm = pusch_pdu->maintenance_parms_v3.tbSizeLbrmBytes;
-    pushTpool(phy_vars_gNB->threadPool,req);
+    pushTpool(&phy_vars_gNB->threadPool, req);
     phy_vars_gNB->nbDecode++;
     LOG_D(PHY,"Added a block to decode, in pipe: %d\n",phy_vars_gNB->nbDecode);
     r_offset += E;
diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c
index 1635c8bba9c30b69a948b7ad94818b3aa77f1386..e6833c4cb45f78a690f30ea42e261d43e5f94a67 100644
--- a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c
+++ b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c
@@ -207,8 +207,8 @@ bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool
 
   } else {
     if ( !last ) {
-      int nb=abortTpool(&(pool_dl), req->key);
-      nb+=abortNotifiedFIFO(nf_p, req->key);
+      int nb=abortTpoolJob(&(pool_dl), req->key);
+      nb+=abortNotifiedFIFOJob(nf_p, req->key);
       LOG_D(PHY,"downlink segment error %d/%d, aborted %d segments\n",rdata->segment_r,rdata->nbSegments, nb);
       LOG_D(PHY, "DLSCH %d in error\n",rdata->dlsch_id);
       last = true;
@@ -615,6 +615,8 @@ uint32_t nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue,
   }
   for (r=0; r<nbDecode; r++) {
     notifiedFIFO_elt_t *req=pullTpool(&nf, &(pool_dl));
+    if (req == NULL)
+      break; // Tpool has been stopped
     bool last = false;
     if (r == nbDecode - 1)
       last = true;
diff --git a/openair1/PHY/defs_common.h b/openair1/PHY/defs_common.h
index c93e3a7de9c0fa32b5e81b6305d3a9184d4ff107..892fb7c8644344529aadb050d54fc8f569d2970b 100644
--- a/openair1/PHY/defs_common.h
+++ b/openair1/PHY/defs_common.h
@@ -982,13 +982,14 @@ typedef uint8_t(encoder_if_t)(uint8_t *input,
                               uint8_t *output,
                               uint8_t F);
 
+extern int oai_exit;
 
 static inline void wait_sync(char *thread_name) {
   int rc;
   printf( "waiting for sync (%s,%d/%p,%p,%p)\n",thread_name,sync_var,&sync_var,&sync_cond,&sync_mutex);
   AssertFatal((rc = pthread_mutex_lock( &sync_mutex ))==0,"sync mutex lock error");
 
-  while (sync_var<0)
+  while (sync_var<0 && !oai_exit)
     pthread_cond_wait( &sync_cond, &sync_mutex );
 
   AssertFatal((rc = pthread_mutex_unlock( &sync_mutex ))==0,"sync mutex unlock error");
@@ -1011,7 +1012,7 @@ static inline int wakeup_thread(pthread_mutex_t *mutex,
   int sleep_cnt=0;
   AssertFatal((rc = pthread_mutex_lock(mutex))==0,"wakeup_thread(): error locking mutex for %s (%d %s, %p)\n", name, rc, strerror(rc), (void *)mutex);
 
-  while (*instance_cnt == 0) {
+  while (*instance_cnt == 0 && !oai_exit) {
     AssertFatal((rc = pthread_mutex_unlock(mutex))==0,"wakeup_thread(): error unlocking mutex for %s (%d %s, %p)\n", name, rc, strerror(rc), (void *)mutex);
     sleep_cnt++;
 
@@ -1046,7 +1047,7 @@ static inline int timedwait_on_condition(pthread_mutex_t *mutex,
   struct timespec now, abstime;
   AssertFatal((rc = pthread_mutex_lock(mutex))==0,"[SCHED][eNB] timedwait_on_condition(): error locking mutex for %s (%d %s, %p)\n", name, rc, strerror(rc), (void *)mutex);
 
-  while (*instance_cnt < 0) {
+  while (*instance_cnt < 0 && !oai_exit) {
     clock_gettime(CLOCK_REALTIME, &now);
     // most of the time the thread is waiting here
     // proc->instance_cnt_rxtx is -1
@@ -1073,7 +1074,7 @@ static inline int wait_on_condition(pthread_mutex_t *mutex,
   int rc;
   AssertFatal((rc = pthread_mutex_lock(mutex))==0,"[SCHED][eNB] wait_on_condition(): error locking mutex for %s (%d %s, %p)\n", name, rc, strerror(rc), (void *)mutex);
 
-  while (*instance_cnt < 0) {
+  while (*instance_cnt < 0 && !oai_exit) {
     // most of the time the thread is waiting here
     // proc->instance_cnt_rxtx is -1
     pthread_cond_wait(cond,mutex); // this unlocks mutex_rxtx while waiting and then locks it again
@@ -1091,7 +1092,7 @@ static inline int wait_on_busy_condition(pthread_mutex_t *mutex,
   int rc;
   AssertFatal((rc = pthread_mutex_lock(mutex))==0,"[SCHED][eNB] wait_on_busy_condition(): error locking mutex for %s (%d %s, %p)\n", name, rc, strerror(rc), (void *)mutex);
 
-  while (*instance_cnt == 0) {
+  while (*instance_cnt == 0 && !oai_exit) {
     // most of the time the thread will skip this
     // waits only if proc->instance_cnt_rxtx is 0
     pthread_cond_wait(cond,mutex); // this unlocks mutex_rxtx while waiting and then locks it again
diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h
index f4387a372bcaac001f6cc97cadfc8203542187d4..ef66bf5fef808c3dff1c6c0aa0a17fd5f0542f9a 100644
--- a/openair1/PHY/defs_gNB.h
+++ b/openair1/PHY/defs_gNB.h
@@ -396,10 +396,6 @@ typedef struct {
 } NR_gNB_SRS_t;
 
 typedef struct {
-  /// \brief Pointers (dynamic) to the received data in the time domain.
-  /// - first index: rx antenna [0..nb_antennas_rx[
-  /// - second index: ? [0..2*ofdm_symbol_size*frame_parms->symbols_per_tti[
-  int32_t **rxdata;
   /// \brief Pointers (dynamic) to the received data in the frequency domain.
   /// - first index: rx antenna [0..nb_antennas_rx[
   /// - second index: ? [0..2*ofdm_symbol_size*frame_parms->symbols_per_tti[
@@ -890,13 +886,13 @@ typedef struct PHY_VARS_gNB_s {
   time_stats_t rx_dft_stats;
   time_stats_t ulsch_freq_offset_estimation_stats;
   */
-  notifiedFIFO_t *respDecode;
-  notifiedFIFO_t *resp_L1;
-  notifiedFIFO_t *L1_tx_free;
-  notifiedFIFO_t *L1_tx_filled;
-  notifiedFIFO_t *L1_tx_out;
-  notifiedFIFO_t *resp_RU_tx;
-  tpool_t *threadPool;
+  notifiedFIFO_t respDecode;
+  notifiedFIFO_t resp_L1;
+  notifiedFIFO_t L1_tx_free;
+  notifiedFIFO_t L1_tx_filled;
+  notifiedFIFO_t L1_tx_out;
+  notifiedFIFO_t resp_RU_tx;
+  tpool_t threadPool;
   int nbDecode;
   uint8_t thread_pool_size;
   int number_of_nr_dlsch_max;
diff --git a/openair1/SCHED/phy_procedures_lte_eNb.c b/openair1/SCHED/phy_procedures_lte_eNb.c
index 1a35f6ee683753181b82db2c3ce4951eafb1b194..db03a9dc437c473e5a592a09d81e29edd929a0fa 100644
--- a/openair1/SCHED/phy_procedures_lte_eNb.c
+++ b/openair1/SCHED/phy_procedures_lte_eNb.c
@@ -432,7 +432,10 @@ bool dlsch_procedures(PHY_VARS_eNB *eNB,
     if ( proc->threadPool->activated ) {
     // Wait all other threads finish to process
     while (proc->nbEncode) {
-      delNotifiedFIFO_elt(pullTpool(proc->respEncode, proc->threadPool));
+      notifiedFIFO_elt_t *res = pullTpool(proc->respEncode, proc->threadPool);
+      if (res == NULL)
+        break; // Tpool has been stopped
+      delNotifiedFIFO_elt(res);
       proc->nbEncode--;
     }
   }
@@ -1328,8 +1331,8 @@ void postDecode(L1_rxtx_proc_t *proc, notifiedFIFO_elt_t *req) {
 	   sz);
   } else {
     if ( rdata->nbSegments != ulsch_harq->processedSegments ) {
-      int nb=abortTpool(proc->threadPool, req->key);
-      nb+=abortNotifiedFIFO(proc->respDecode, req->key);
+      int nb=abortTpoolJob(proc->threadPool, req->key);
+      nb+=abortNotifiedFIFOJob(proc->respDecode, req->key);
       proc->nbDecode-=nb;
       LOG_D(PHY,"uplink segment error %d/%d, aborted %d segments\n",rdata->segment_r,rdata->nbSegments, nb);
       AssertFatal(ulsch_harq->processedSegments+nb == rdata->nbSegments,"processed: %d, aborted: %d, total %d\n",
@@ -1502,6 +1505,8 @@ void pusch_procedures(PHY_VARS_eNB *eNB,L1_rxtx_proc_t *proc) {
   
   while (proc->nbDecode > 0) {
     notifiedFIFO_elt_t *req=pullTpool(proc->respDecode, proc->threadPool);
+    if (req == NULL)
+      break; // Tpool has been stopped
     postDecode(proc, req);
     delNotifiedFIFO_elt(req);
   }
diff --git a/openair1/SCHED_NR/fapi_nr_l1.c b/openair1/SCHED_NR/fapi_nr_l1.c
index 5c08a0f479f820785ef5cf83d29481e0cfcf1619..49e6edd54e6400fd400c7b02e7186d6c77724d18 100644
--- a/openair1/SCHED_NR/fapi_nr_l1.c
+++ b/openair1/SCHED_NR/fapi_nr_l1.c
@@ -163,7 +163,9 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO){
 
     if (slot_type == NR_DOWNLINK_SLOT || slot_type == NR_MIXED_SLOT) {
       notifiedFIFO_elt_t *res;
-      res = pullTpool(gNB->L1_tx_free, gNB->threadPool);
+      res = pullTpool(&gNB->L1_tx_free, &gNB->threadPool);
+      if (res == NULL)
+        return; // Tpool has been stopped, nothing to process
       processingData_L1tx_t *msgTx = (processingData_L1tx_t *)NotifiedFifoData(res);
       const time_stats_t ts = exec_time_stats_NotifiedFIFO(res);
       merge_meas(&gNB->phy_proc_tx, &ts);
@@ -210,7 +212,7 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO){
       for (int i=0; i<number_ul_dci_pdu; i++)
         msgTx->ul_pdcch_pdu[i] = UL_dci_req->ul_dci_pdu_list[i];
 
-      pushNotifiedFIFO(gNB->L1_tx_filled,res);
+      pushNotifiedFIFO(&gNB->L1_tx_filled,res);
     }
 
     for (int i = 0; i < number_ul_tti_pdu; i++) {
diff --git a/openair1/SCHED_NR/nr_ru_procedures.c b/openair1/SCHED_NR/nr_ru_procedures.c
index 3d5e4a0ce4d7b1f8a424b91e303fbc9126d6d376..a93ac0ded2c5f45fc153132b963aac0d50f7145b 100644
--- a/openair1/SCHED_NR/nr_ru_procedures.c
+++ b/openair1/SCHED_NR/nr_ru_procedures.c
@@ -453,6 +453,24 @@ void nr_init_feptx_thread(RU_t *ru) {
 
 }
 
+void nr_kill_feptx_thread(RU_t *ru) {
+  RU_proc_t *proc = &ru->proc;
+
+  for (int i = 0; i < ru->nb_tx * 2; i++) {
+    RU_feptx_t *feptx = &proc->feptx[i];
+    if (feptx->pthread_feptx == 0)
+      continue;
+
+    pthread_mutex_lock(&feptx->mutex_feptx);
+    feptx->instance_cnt_feptx = 0;
+    pthread_mutex_unlock(&feptx->mutex_feptx);
+    pthread_cond_signal(&feptx->cond_feptx);
+    LOG_I(PHY, "Joining pthread_feptx %d\n", i);
+    pthread_join(feptx->pthread_feptx, NULL);
+    pthread_mutex_destroy(&feptx->mutex_feptx);
+    pthread_cond_destroy(&feptx->cond_feptx);
+  }
+}
 
 void nr_feptx_prec(RU_t *ru,int frame_tx,int tti_tx) {
 
@@ -590,6 +608,19 @@ void nr_init_feprx_thread(RU_t *ru) {
   threadCreate(&proc->pthread_fep, nr_feprx_thread, (void*)ru, "feprx", -1, OAI_PRIORITY_RT);
 }
 
+void nr_kill_feprx_thread(RU_t *ru) {
+  RU_proc_t *proc = &ru->proc;
+  if (proc->pthread_fep == 0)
+    return;
+  LOG_I(PHY, "Joining pthread_feprx\n");
+  pthread_mutex_lock(&proc->mutex_fep);
+  proc->instance_cnt_fep = 0;
+  pthread_mutex_unlock(&proc->mutex_fep);
+  pthread_cond_signal(&proc->cond_fep);
+  pthread_join(proc->pthread_fep, NULL);
+  pthread_mutex_destroy(&proc->mutex_fep);
+  pthread_cond_destroy(&proc->cond_fep);
+}
 
 void nr_fep_full_2thread(RU_t *ru, int slot) {
 
@@ -617,8 +648,7 @@ void nr_fep_full_2thread(RU_t *ru, int slot) {
   }
 
   if (proc->instance_cnt_fep==0) {
-    printf("[RU] FEP thread busy\n");
-    exit_fun("FEP thread busy");
+    LOG_E(PHY, "RU FEP thread busy, exiting %s\n", __func__);
     pthread_mutex_unlock( &proc->mutex_fep );
     return;
   }
diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c
index 978c1d1be92c5a478f9837a93c0ade0edc269813..7c6a2eb998f5c704d1fb58c9eb05b7e4068ce965 100644
--- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c
+++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c
@@ -206,8 +206,8 @@ void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) {
 
   } else {
     if ( rdata->nbSegments != ulsch_harq->processedSegments ) {
-      int nb=abortTpool(gNB->threadPool, req->key);
-      nb+=abortNotifiedFIFO(gNB->respDecode, req->key);
+      int nb = abortTpoolJob(&gNB->threadPool, req->key);
+      nb += abortNotifiedFIFOJob(&gNB->respDecode, req->key);
       gNB->nbDecode-=nb;
       LOG_D(PHY,"uplink segment error %d/%d, aborted %d segments\n",rdata->segment_r,rdata->nbSegments, nb);
       LOG_D(PHY, "ULSCH %d in error\n",rdata->ulsch_id);
@@ -363,7 +363,9 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH
                     G);
 
   while (gNB->nbDecode > 0) {
-    notifiedFIFO_elt_t *req=pullTpool(gNB->respDecode, gNB->threadPool);
+    notifiedFIFO_elt_t *req = pullTpool(&gNB->respDecode, &gNB->threadPool);
+    if (req == NULL)
+      break; // Tpool has been stopped
     nr_postDecode(gNB, req);
     delNotifiedFIFO_elt(req);
   }
@@ -580,33 +582,6 @@ void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) {
 
 }
 
-void phy_procedures_gNB_common_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) {
-
-  uint8_t symbol;
-  unsigned char aa;
-
-  for(symbol = 0; symbol < (gNB->frame_parms.Ncp==EXTENDED?12:14); symbol++) {
-    for (aa = 0; aa < gNB->frame_parms.nb_antennas_rx; aa++) {
-      nr_slot_fep_ul(&gNB->frame_parms,
-                     gNB->common_vars.rxdata[aa],
-                     gNB->common_vars.rxdataF[aa],
-                     symbol,
-                     slot_rx,
-                     0);
-    }
-  }
-
-  for (aa = 0; aa < gNB->frame_parms.nb_antennas_rx; aa++) {
-    apply_nr_rotation_ul(&gNB->frame_parms,
-			 gNB->common_vars.rxdataF[aa],
-			 slot_rx,
-			 0,
-			 gNB->frame_parms.Ncp==EXTENDED?12:14,
-			 gNB->frame_parms.ofdm_symbol_size);
-  }
-
-}
-
 int fill_srs_reported_symbol_list(nfapi_nr_srs_indication_reported_symbol_t *reported_symbol_list,
                                   const nfapi_nr_srs_pdu_t *srs_pdu,
                                   const int N_RB_UL,
diff --git a/openair1/SCHED_NR/sched_nr.h b/openair1/SCHED_NR/sched_nr.h
index 2124397c47eac5fe700bdf5aecae38f6bfb5306b..eff8a9fbcd3fb8586819b070d8ca9686a5c515ed 100644
--- a/openair1/SCHED_NR/sched_nr.h
+++ b/openair1/SCHED_NR/sched_nr.h
@@ -37,7 +37,6 @@
 void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx);
 void nr_set_ssb_first_subcarrier(nfapi_nr_config_request_scf_t *cfg, NR_DL_FRAME_PARMS *fp);
 void phy_procedures_gNB_TX(processingData_L1tx_t *msgTx, int frame_tx, int slot_tx, int do_meas);
-void phy_procedures_gNB_common_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx);
 int  phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx);
 void L1_nr_prach_procedures(PHY_VARS_gNB *gNB,int frame,int slot);
 void nr_common_signal_procedures (PHY_VARS_gNB *gNB,int frame,int slot,nfapi_nr_dl_tti_ssb_pdu ssb_pdu);
@@ -45,11 +44,13 @@ void nr_feptx_ofdm(RU_t *ru,int frame_tx,int tti_tx);
 void nr_feptx_ofdm_2thread(RU_t *ru,int frame_tx,int tti_tx);
 void nr_feptx0(RU_t *ru,int tti_tx,int first_symbol, int num_symbols, int aa);
 void nr_init_feptx_thread(RU_t *ru);
+void nr_kill_feptx_thread(RU_t *ru);
 void fep_full(RU_t *ru,int slot);
 void nr_feptx_prec(RU_t *ru,int frame_tx,int tti_tx);
 void nr_init_feptx_prec_thread(RU_t *ru);
 void nr_feptx_prec_control(RU_t *ru,int frame,int tti_tx);
 void nr_init_feprx_thread(RU_t *ru);
+void nr_kill_feprx_thread(RU_t *ru);
 void nr_fep_full(RU_t *ru, int slot);
 void nr_fep_full_2thread(RU_t *ru, int slot);
 void feptx_prec(RU_t *ru,int frame_tx,int tti_tx);
diff --git a/openair1/SIMULATION/LTE_PHY/unitary_defs.h b/openair1/SIMULATION/LTE_PHY/unitary_defs.h
index 10a5366082fbf61fe9b47430cc4c91cc1a811c75..342fa18fb9645db1dfa0e3152e0739cb026c3845 100644
--- a/openair1/SIMULATION/LTE_PHY/unitary_defs.h
+++ b/openair1/SIMULATION/LTE_PHY/unitary_defs.h
@@ -20,7 +20,7 @@
  */
 
 openair0_device openair0;
-volatile int oai_exit=0;
+int oai_exit=0;
 
 void exit_function(const char* file, const char* function, const int line,const char *s) {
    const char * msg= s==NULL ? "no comment": s;
diff --git a/openair1/SIMULATION/NR_PHY/dlschsim.c b/openair1/SIMULATION/NR_PHY/dlschsim.c
index 72a4774f0566817b2b274dc9a734b8b1d7036f7c..f718aad9f0438026c6d7b9dac69c1245eeb23993 100644
--- a/openair1/SIMULATION/NR_PHY/dlschsim.c
+++ b/openair1/SIMULATION/NR_PHY/dlschsim.c
@@ -363,8 +363,7 @@ int main(int argc, char **argv)
 	RC.gNB = (PHY_VARS_gNB **) malloc(sizeof(PHY_VARS_gNB *));
 	RC.gNB[0] = calloc(1, sizeof(PHY_VARS_gNB));
 	gNB = RC.gNB[0];
-	gNB->threadPool = (tpool_t*)malloc(sizeof(tpool_t));
-	initTpool(gNBthreads, gNB->threadPool, true);
+	initTpool(gNBthreads, &gNB->threadPool, true);
 	//gNB_config = &gNB->gNB_config;
 	frame_parms = &gNB->frame_parms; //to be initialized I suppose (maybe not necessary for PBCH)
 	frame_parms->nb_antennas_tx = n_tx;
@@ -625,7 +624,6 @@ int main(int argc, char **argv)
   reset_DLSCH_struct(gNB, &msgDataTx);
 
   phy_free_nr_gNB(gNB);
-  free(gNB->threadPool);
   free(RC.gNB[0]);
   free(RC.gNB);
 
diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c
index 9cf4d3742aa17248e9a2fff2341d3a06aff2861d..0f573789469d3a0f184c5e04e13faeee4a625974 100644
--- a/openair1/SIMULATION/NR_PHY/dlsim.c
+++ b/openair1/SIMULATION/NR_PHY/dlsim.c
@@ -1042,16 +1042,12 @@ int main(int argc, char **argv)
   snrRun = 0;
   int n_errs = 0;
 
-  gNB->threadPool = (tpool_t*)malloc(sizeof(tpool_t));
-  initTpool(gNBthreads, gNB->threadPool, true);
-  gNB->L1_tx_free = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  gNB->L1_tx_filled = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  gNB->L1_tx_out = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  initNotifiedFIFO(gNB->L1_tx_free);
-  initNotifiedFIFO(gNB->L1_tx_filled);
-  initNotifiedFIFO(gNB->L1_tx_out);
+  initTpool(gNBthreads, &gNB->threadPool, true);
+  initNotifiedFIFO(&gNB->L1_tx_free);
+  initNotifiedFIFO(&gNB->L1_tx_filled);
+  initNotifiedFIFO(&gNB->L1_tx_out);
   // we create 2 threads for L1 tx processing
-  notifiedFIFO_elt_t *msgL1Tx = newNotifiedFIFO_elt(sizeof(processingData_L1tx_t),0,gNB->L1_tx_free,processSlotTX);
+  notifiedFIFO_elt_t *msgL1Tx = newNotifiedFIFO_elt(sizeof(processingData_L1tx_t),0,&gNB->L1_tx_free,processSlotTX);
   processingData_L1tx_t *msgDataTx = (processingData_L1tx_t *)NotifiedFifoData(msgL1Tx);
   init_DLSCH_struct(gNB, msgDataTx);
   msgDataTx->slot = slot;
@@ -1127,7 +1123,7 @@ int main(int argc, char **argv)
         Sched_INFO.UL_tti_req    = gNB_mac->UL_tti_req_ahead[0];
         Sched_INFO.UL_dci_req  = NULL;
         Sched_INFO.TX_req    = &gNB_mac->TX_req[0];
-        pushNotifiedFIFO(gNB->L1_tx_free,msgL1Tx);
+        pushNotifiedFIFO(&gNB->L1_tx_free,msgL1Tx);
         nr_schedule_response(&Sched_INFO);
 
         /* PTRS values for DLSIM calculations   */
diff --git a/openair1/SIMULATION/NR_PHY/nr_unitary_defs.h b/openair1/SIMULATION/NR_PHY/nr_unitary_defs.h
index 805913d055511bae547898822e9a3e8681bf4c4c..d3af2acd77f14041ed64cb44593eb5582588cf28 100644
--- a/openair1/SIMULATION/NR_PHY/nr_unitary_defs.h
+++ b/openair1/SIMULATION/NR_PHY/nr_unitary_defs.h
@@ -33,7 +33,7 @@
 #ifndef __NR_UNITARY_DEFS__H__
 #define __NR_UNITARY_DEFS__H__
 
-volatile int oai_exit=0;
+int oai_exit=0;
 
 void exit_function(const char* file, const char* function, const int line, const char *s) {
   const char * msg= s==NULL ? "no comment": s;
diff --git a/openair1/SIMULATION/NR_PHY/pucchsim.c b/openair1/SIMULATION/NR_PHY/pucchsim.c
index df83a818c0081759714d69fb54417ccdabb06eec..1d1d2f8fa5c41caaa811348ad9fcd4d53214922f 100644
--- a/openair1/SIMULATION/NR_PHY/pucchsim.c
+++ b/openair1/SIMULATION/NR_PHY/pucchsim.c
@@ -390,6 +390,10 @@ int main(int argc, char **argv)
   cfg->carrier_config.num_rx_ant.value = n_rx;
   nr_phy_config_request_sim(gNB,N_RB_DL,N_RB_DL,mu,Nid_cell,SSB_positions);
   phy_init_nr_gNB(gNB,0,0);
+  /* RU handles rxdataF, and gNB just has a pointer. Here, we don't have an RU,
+   * so we need to allocate that memory as well. */
+  for (i = 0; i < n_rx; i++)
+    gNB->common_vars.rxdataF[i] = malloc16_clear(gNB->frame_parms.samples_per_frame_wCP*sizeof(int32_t));
 
   double fs,txbw,rxbw;
   uint32_t samples;
@@ -702,6 +706,8 @@ int main(int argc, char **argv)
   free_channel_desc_scm(UE2gNB);
   term_freq_channel();
 
+  for (i = 0; i < n_rx; i++)
+    free(gNB->common_vars.rxdataF[i]);
   phy_free_nr_gNB(gNB);
   free(RC.gNB[0]);
   free(RC.gNB);
diff --git a/openair1/SIMULATION/NR_PHY/ulschsim.c b/openair1/SIMULATION/NR_PHY/ulschsim.c
index 5392424285729463be925948ee877d336209cc7d..222cd1da7d9d286556f7029219ac5af397a56600 100644
--- a/openair1/SIMULATION/NR_PHY/ulschsim.c
+++ b/openair1/SIMULATION/NR_PHY/ulschsim.c
@@ -87,8 +87,8 @@ int nr_postDecode_sim(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) {
            rdata->Kr_bytes - (ulsch_harq->F>>3) -((ulsch_harq->C>1)?3:0));
   } else {
     if ( rdata->nbSegments != ulsch_harq->processedSegments ) {
-      int nb=abortTpool(gNB->threadPool, req->key);
-      nb+=abortNotifiedFIFO(gNB->respDecode, req->key);
+      int nb=abortTpoolJob(&gNB->threadPool, req->key);
+      nb+=abortNotifiedFIFOJob(&gNB->respDecode, req->key);
       gNB->nbDecode-=nb;
       AssertFatal(ulsch_harq->processedSegments+nb == rdata->nbSegments,"processed: %d, aborted: %d, total %d\n",
       ulsch_harq->processedSegments, nb, rdata->nbSegments);
@@ -387,11 +387,9 @@ int main(int argc, char **argv)
   gNB = RC.gNB[0];
   //gNB_config = &gNB->gNB_config;
 
-  gNB->threadPool = (tpool_t*)malloc(sizeof(tpool_t));
-  gNB->respDecode = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
   char tp_param[] = "n";
-  initTpool(tp_param, gNB->threadPool, true);
-  initNotifiedFIFO(gNB->respDecode);
+  initTpool(tp_param, &gNB->threadPool, true);
+  initNotifiedFIFO(&gNB->respDecode);
   frame_parms = &gNB->frame_parms; //to be initialized I suppose (maybe not necessary for PBCH)
   frame_parms->N_RB_DL = N_RB_DL;
   frame_parms->N_RB_UL = N_RB_UL;
@@ -586,7 +584,7 @@ int main(int argc, char **argv)
       nr_ulsch_decoding(gNB, UE_id, channel_output_fixed, frame_parms, rel15_ul,
                               frame, subframe, harq_pid, G);
       while (gNB->nbDecode > 0) {
-        notifiedFIFO_elt_t *req=pullTpool(gNB->respDecode, gNB->threadPool);
+        notifiedFIFO_elt_t *req=pullTpool(&gNB->respDecode, &gNB->threadPool);
         ret = nr_postDecode_sim(gNB, req);
         delNotifiedFIFO_elt(req);
       }
@@ -636,8 +634,6 @@ int main(int argc, char **argv)
   free(UE);
 
   phy_free_nr_gNB(gNB);
-  free(gNB->threadPool);
-  free(gNB->respDecode);
   free(RC.gNB[0]);
   free(RC.gNB);
 
diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c
index 3ac1ec3439834af0513c849ffd6ec33bd1a7cb52..f4dece26ffd5d65f0ed2c43efe29c114971ded6c 100644
--- a/openair1/SIMULATION/NR_PHY/ulsim.c
+++ b/openair1/SIMULATION/NR_PHY/ulsim.c
@@ -700,9 +700,6 @@ int main(int argc, char **argv)
   RC.gNB[0] = calloc(1,sizeof(PHY_VARS_gNB));
   gNB = RC.gNB[0];
   gNB->ofdm_offset_divisor = UINT_MAX;
-  gNB->threadPool = (tpool_t*)malloc(sizeof(tpool_t));
-  gNB->respDecode = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  initNotifiedFIFO(gNB->respDecode);
   char tp_param[80];
   if (threadCnt>0)
    sprintf(tp_param,"-1");
@@ -714,15 +711,12 @@ int main(int argc, char **argv)
     s_offset += 3;
   }
 
-  initTpool(tp_param, gNB->threadPool, false);
-  initNotifiedFIFO(gNB->respDecode);
-  gNB->L1_tx_free = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  gNB->L1_tx_filled = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  gNB->L1_tx_out = (notifiedFIFO_t*) malloc(sizeof(notifiedFIFO_t));
-  initNotifiedFIFO(gNB->L1_tx_free);
-  initNotifiedFIFO(gNB->L1_tx_filled);
-  initNotifiedFIFO(gNB->L1_tx_out);
-  notifiedFIFO_elt_t *msgL1Tx = newNotifiedFIFO_elt(sizeof(processingData_L1tx_t),0,gNB->L1_tx_free,NULL);
+  initTpool(tp_param, &gNB->threadPool, false);
+  initNotifiedFIFO(&gNB->respDecode);
+  initNotifiedFIFO(&gNB->L1_tx_free);
+  initNotifiedFIFO(&gNB->L1_tx_filled);
+  initNotifiedFIFO(&gNB->L1_tx_out);
+  notifiedFIFO_elt_t *msgL1Tx = newNotifiedFIFO_elt(sizeof(processingData_L1tx_t), 0, &gNB->L1_tx_free, NULL);
   processingData_L1tx_t *msgDataTx = (processingData_L1tx_t *)NotifiedFifoData(msgL1Tx);
   msgDataTx->slot = -1;
   //gNB_config = &gNB->gNB_config;
@@ -800,8 +794,17 @@ int main(int argc, char **argv)
   gNB->chest_freq = chest_type[0];
   gNB->chest_time = chest_type[1];
   phy_init_nr_gNB(gNB,0,1);
+  /* RU handles rxdataF, and gNB just has a pointer. Here, we don't have an RU,
+   * so we need to allocate that memory as well. */
+  for (i = 0; i < n_rx; i++)
+    gNB->common_vars.rxdataF[i] = malloc16_clear(gNB->frame_parms.samples_per_frame_wCP*sizeof(int32_t));
   N_RB_DL = gNB->frame_parms.N_RB_DL;
 
+  /* no RU: need to have rxdata */
+  c16_t **rxdata;
+  rxdata = malloc(n_rx * sizeof(*rxdata));
+  for (int i = 0; i < n_rx; ++i)
+    rxdata[i] = malloc(gNB->frame_parms.samples_per_frame * sizeof(**rxdata));
 
   NR_BWP_Uplink_t *ubwp=secondaryCellGroup->spCellConfig->spCellConfigDedicated->uplinkConfig->uplinkBWP_ToAddModList->list.array[0];
 
@@ -1031,18 +1034,16 @@ int main(int argc, char **argv)
     fseek(input_fd,file_offset*sizeof(int16_t)*2,SEEK_SET);
     for (int irx=0; irx<frame_parms->nb_antennas_rx; irx++) {
       fseek(input_fd,irx*(slot_length+15)*sizeof(int16_t)*2,SEEK_SET); // matlab adds samlples to the end to emulate channel delay
-      read_errors+=fread((void*)&gNB->common_vars.rxdata[irx][slot_offset-delay],
-      sizeof(int16_t),
-      slot_length<<1,
-      input_fd);
+      read_errors += fread((void *)&rxdata[irx][slot_offset-delay], sizeof(int16_t), slot_length<<1, input_fd);
       if (read_errors==0) {
         printf("error reading file\n");
         exit(1);
       }
-      for (int i=0;i<16;i+=2) printf("slot_offset %d : %d,%d\n",
-             slot_offset,
-             ((int16_t*)&gNB->common_vars.rxdata[irx][slot_offset])[i],
-             ((int16_t*)&gNB->common_vars.rxdata[irx][slot_offset])[1+i]);
+      for (int i=0;i<16;i+=2)
+        printf("slot_offset %d : %d,%d\n",
+               slot_offset,
+               rxdata[irx][slot_offset].r,
+               rxdata[irx][slot_offset].i);
     }
 
     mod_order = nr_get_Qm_ul(Imcs, mcs_table);
@@ -1168,7 +1169,7 @@ int main(int argc, char **argv)
       }
 
       // prepare ULSCH/PUSCH reception
-      pushNotifiedFIFO(gNB->L1_tx_free,msgL1Tx); // to unblock the process in the beginning
+      pushNotifiedFIFO(&gNB->L1_tx_free, msgL1Tx); // to unblock the process in the beginning
       nr_schedule_response(Sched_INFO);
 
       // --------- setting parameters for UE --------
@@ -1331,12 +1332,11 @@ int main(int argc, char **argv)
 
         for (i=0; i<slot_length; i++) {
           for (ap=0; ap<frame_parms->nb_antennas_rx; ap++) {
-            ((int16_t*) &gNB->common_vars.rxdata[ap][slot_offset])[(2*i)   + (delay*2)] = (int16_t)((r_re[ap][i]) + (sqrt(sigma/2)*gaussdouble(0.0,1.0))); // convert to fixed point
-            ((int16_t*) &gNB->common_vars.rxdata[ap][slot_offset])[(2*i)+1 + (delay*2)] = (int16_t)((r_im[ap][i]) + (sqrt(sigma/2)*gaussdouble(0.0,1.0)));
+            rxdata[ap][slot_offset+i+delay].r = (int16_t)((r_re[ap][i]) + (sqrt(sigma/2)*gaussdouble(0.0,1.0))); // convert to fixed point
+            rxdata[ap][slot_offset+i+delay].i = (int16_t)((r_im[ap][i]) + (sqrt(sigma/2)*gaussdouble(0.0,1.0)));
             /* Add phase noise if enabled */
             if (pdu_bit_map & PUSCH_PDU_BITMAP_PUSCH_PTRS) {
-              phase_noise(ts, &((int16_t*)&gNB->common_vars.rxdata[ap][slot_offset])[(2*i)],
-                          &((int16_t*)&gNB->common_vars.rxdata[ap][slot_offset])[(2*i)+1]);
+              phase_noise(ts, &rxdata[ap][slot_offset].r, &rxdata[ap][slot_offset].i);
             }
           }
         }
@@ -1362,19 +1362,36 @@ int main(int argc, char **argv)
 	gNB->UL_INFO.rx_ind.number_of_pdus = 0;
 	gNB->UL_INFO.crc_ind.number_crcs = 0;
 
-    phy_procedures_gNB_common_RX(gNB, frame, slot);
+        for(uint8_t symbol = 0; symbol < (gNB->frame_parms.Ncp == EXTENDED ? 12 : 14); symbol++) {
+          for (int aa = 0; aa < gNB->frame_parms.nb_antennas_rx; aa++)
+            nr_slot_fep_ul(&gNB->frame_parms,
+                           (int32_t*) rxdata[aa],
+                           gNB->common_vars.rxdataF[aa],
+                           symbol,
+                           slot,
+                           0);
+        }
+
+        for (int aa = 0; aa < gNB->frame_parms.nb_antennas_rx; aa++)  {
+          apply_nr_rotation_ul(&gNB->frame_parms,
+                               gNB->common_vars.rxdataF[aa],
+                               slot,
+                               0,
+                               gNB->frame_parms.Ncp == EXTENDED ? 12 : 14,
+                               gNB->frame_parms.ofdm_symbol_size);
+        }
 
     ul_proc_error = phy_procedures_gNB_uespec_RX(gNB, frame, slot);
 
     if (n_trials==1 && round==0) {
-      LOG_M("rxsig0.m","rx0",&gNB->common_vars.rxdata[0][slot_offset],slot_length,1,1);
+      LOG_M("rxsig0.m","rx0",&rxdata[0][slot_offset],slot_length,1,1);
       LOG_M("rxsigF0.m","rxsF0",gNB->common_vars.rxdataF[0],14*frame_parms->ofdm_symbol_size,1,1);
       if (precod_nbr_layers > 1) {
-        LOG_M("rxsig1.m","rx1",&gNB->common_vars.rxdata[1][slot_offset],slot_length,1,1);
+        LOG_M("rxsig1.m","rx1",&rxdata[1][slot_offset],slot_length,1,1);
         LOG_M("rxsigF1.m","rxsF1",gNB->common_vars.rxdataF[1],14*frame_parms->ofdm_symbol_size,1,1);
         if (precod_nbr_layers==4) {
-          LOG_M("rxsig2.m","rx2",&gNB->common_vars.rxdata[2][slot_offset],slot_length,1,1);
-          LOG_M("rxsig3.m","rx3",&gNB->common_vars.rxdata[3][slot_offset],slot_length,1,1);
+          LOG_M("rxsig2.m","rx2",&rxdata[2][slot_offset],slot_length,1,1);
+          LOG_M("rxsig3.m","rx3",&rxdata[3][slot_offset],slot_length,1,1);
           LOG_M("rxsigF2.m","rxsF2",gNB->common_vars.rxdataF[2],14*frame_parms->ofdm_symbol_size,1,1);
           LOG_M("rxsigF3.m","rxsF3",gNB->common_vars.rxdataF[3],14*frame_parms->ofdm_symbol_size,1,1);
         }
diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c
index 0f4e0539f263146b009efcd361edb4d315c5426f..770bae5b5d8e033c1e88111a4acc44600bd1bd16 100644
--- a/openair2/LAYER2/NR_MAC_gNB/config.c
+++ b/openair2/LAYER2/NR_MAC_gNB/config.c
@@ -489,23 +489,21 @@ int rrc_mac_config_req_gNB(module_id_t Mod_idP,
 		  scc);
     LOG_D(NR_MAC, "%s() %s:%d RC.nrmac[Mod_idP]->if_inst->NR_PHY_config_req:%p\n", __FUNCTION__, __FILE__, __LINE__, RC.nrmac[Mod_idP]->if_inst->NR_PHY_config_req);
   
-    // if in nFAPI mode 
-    if ( (NFAPI_MODE == NFAPI_MODE_PNF || NFAPI_MODE == NFAPI_MODE_VNF) && (RC.nrmac[Mod_idP]->if_inst->NR_PHY_config_req == NULL) ){
-      while(RC.nrmac[Mod_idP]->if_inst->NR_PHY_config_req == NULL) {
-        // DJP AssertFatal(RC.nrmac[Mod_idP]->if_inst->PHY_config_req != NULL,"if_inst->phy_config_request is null\n");
-        usleep(100 * 1000);
-        printf("Waiting for PHY_config_req\n");
-      }
+    if (NFAPI_MODE == NFAPI_MODE_PNF || NFAPI_MODE == NFAPI_MODE_VNF) {
+      // fake that the gNB is configured in nFAPI mode, which would normally be
+      // done in a NR_PHY_config_req, but in this mode, there is no PHY
+      RC.gNB[Mod_idP]->configured = 1;
+    } else {
+      NR_PHY_Config_t phycfg = {
+        .Mod_id = Mod_idP,
+        .CC_id  = 0,
+        .cfg    = &RC.nrmac[Mod_idP]->config[0]
+      };
+      DevAssert(RC.nrmac[Mod_idP]->if_inst->NR_PHY_config_req);
+      RC.nrmac[Mod_idP]->if_inst->NR_PHY_config_req(&phycfg);
     }
-    RC.nrmac[Mod_idP]->minRXTXTIMEpdsch = minRXTXTIMEpdsch;
-
-    NR_PHY_Config_t phycfg;
-    phycfg.Mod_id = Mod_idP;
-    phycfg.CC_id  = 0;
-    phycfg.cfg    = &RC.nrmac[Mod_idP]->config[0];
-
-    if (RC.nrmac[Mod_idP]->if_inst->NR_PHY_config_req) RC.nrmac[Mod_idP]->if_inst->NR_PHY_config_req(&phycfg);
 
+    RC.nrmac[Mod_idP]->minRXTXTIMEpdsch = minRXTXTIMEpdsch;
     find_SSB_and_RO_available(Mod_idP);
 
     const NR_TDD_UL_DL_Pattern_t *tdd = scc->tdd_UL_DL_ConfigurationCommon ? &scc->tdd_UL_DL_ConfigurationCommon->pattern1 : NULL;
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
index 6aee81fa67237e4c6cb24e3c699fb630a034e13e..a35c80b95a6f0016f32b739b3a46a7ceb2521ff3 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
@@ -613,6 +613,14 @@ void pf_dl(module_id_t module_id,
         return;
 
     } else {
+      /* skip this UE if there are no free HARQ processes. This can happen e.g.
+       * if the UE disconnected in L2sim, in which case the gNB is not notified
+       * (this can be considered a design flaw) */
+      if (sched_ctrl->available_dl_harq.head < 0) {
+        LOG_D(NR_MAC, "RNTI %04x has no free DL HARQ process, skipping\n", UE->rnti);
+        continue;
+      }
+
       /* Check DL buffer and skip this UE if no bytes and no TA necessary */
       if (sched_ctrl->num_total_bytes == 0 && frame != (sched_ctrl->ta_frame + 10) % 1024)
         continue;
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
index c852c83e71fb4f89231e94f84ad1b9932b929068..7f4311c10d89297909a8136c371c36345e2b8077 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
@@ -916,21 +916,26 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id,
       handle_dl_harq(UE, pid, harq_value == 0 && harq_confidence == 0, RC.nrmac[mod_id]->dl_bler.harq_round_max);
       if (harq_confidence == 1)  UE->mac_stats.pucch0_DTX++;
     }
-  }
 
-  // check scheduling request result, confidence_level == 0 is good
-  if (uci_01->pduBitmap & 0x1 && uci_01->sr->sr_indication && uci_01->sr->sr_confidence_level == 0 && uci_01->ul_cqi >= 148) {
-    // SR detected with SNR >= 10dB
-    sched_ctrl->SR |= true;
-    LOG_D(NR_MAC, "SR UE %04x ul_cqi %d\n", uci_01->rnti, uci_01->ul_cqi);
+    // tpc (power control) only if we received AckNack
+    if (uci_01->harq->harq_confidence_level==0)
+      sched_ctrl->tpc1 = nr_get_tpc(RC.nrmac[mod_id]->pucch_target_snrx10, uci_01->ul_cqi, 30);
+    else
+      sched_ctrl->tpc1 = 3;
+    sched_ctrl->pucch_snrx10 = uci_01->ul_cqi * 5 - 640;
+
+    free(uci_01->harq->harq_list);
+    free(uci_01->harq);
   }
 
-  // tpc (power control) only if we received AckNack or positive SR. For a
-  // negative SR, the UE won't have sent anything, and the SNR is not valid
-  if (((uci_01->pduBitmap >> 1) & 0x1) ) {
-    if ((uci_01->harq) && (uci_01->harq->harq_confidence_level==0)) sched_ctrl->tpc1 = nr_get_tpc(RC.nrmac[mod_id]->pucch_target_snrx10, uci_01->ul_cqi, 30);
-    else                                        sched_ctrl->tpc1 = 3;
-    sched_ctrl->pucch_snrx10 = uci_01->ul_cqi * 5 - 640;
+  // check scheduling request result, confidence_level == 0 is good
+  if (uci_01->pduBitmap & 0x1) {
+    if (uci_01->sr->sr_indication && uci_01->sr->sr_confidence_level == 0 && uci_01->ul_cqi >= 148) {
+      // SR detected with SNR >= 10dB
+      sched_ctrl->SR |= true;
+      LOG_D(NR_MAC, "SR UE %04x ul_cqi %d\n", uci_01->rnti, uci_01->ul_cqi);
+    }
+    free(uci_01->sr);
   }
 }
 
@@ -957,6 +962,11 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id,
   //                              30);
   //sched_ctrl->pucch_snrx10 = uci_234->ul_cqi * 5 - 640;
 
+  // TODO: handle SR
+  if (uci_234->pduBitmap & 0x1) {
+    free(uci_234->sr.sr_payload);
+  }
+
   if ((uci_234->pduBitmap >> 1) & 0x01) {
     // iterate over received harq bits
     for (int harq_bit = 0; harq_bit < uci_234->harq.harq_bit_len; harq_bit++) {
@@ -969,15 +979,18 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id,
       remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
       handle_dl_harq(UE, pid, uci_234->harq.harq_crc != 1 && acknack, RC.nrmac[mod_id]->dl_bler.harq_round_max);
     }
+    free(uci_234->harq.harq_payload);
   }
   if ((uci_234->pduBitmap >> 2) & 0x01) {
     //API to parse the csi report and store it into sched_ctrl
     extract_pucch_csi_report(csi_MeasConfig, uci_234, frame, slot, UE, RC.nrmac[mod_id]->common_channels->ServingCellConfigCommon);
     //TCI handling function
     tci_handling(UE,frame, slot);
+    free(uci_234->csi_part1.csi_part1_payload);
   }
   if ((uci_234->pduBitmap >> 3) & 0x01) {
     //@TODO:Handle CSI Report 2
+    // nothing to free (yet)
   }
 }
 
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
index 43e88eb9a943ae22f21c56cb29e52680f4c2ccf1..1ca9fa067e10454247cdeaa2a0eee672a335ee23 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
@@ -1085,6 +1085,15 @@ void pf_ul(module_id_t module_id,
 
       continue;
     } 
+
+    /* skip this UE if there are no free HARQ processes. This can happen e.g.
+     * if the UE disconnected in L2sim, in which case the gNB is not notified
+     * (this can be considered a design flaw) */
+    if (sched_ctrl->available_ul_harq.head < 0) {
+      LOG_D(NR_MAC, "RNTI %04x has no free UL HARQ process, skipping\n", UE->rnti);
+      continue;
+    }
+
     const int B = max(0, sched_ctrl->estimated_ul_buffer - sched_ctrl->sched_ul_bytes);
     /* preprocessor computed sched_frame/sched_slot */
     const bool do_sched = nr_UE_is_to_be_scheduled(scc, 0, UE, sched_pusch->frame, sched_pusch->slot, nrmac->ulsch_max_frame_inactivity);
diff --git a/openair2/LAYER2/PDCP_v10.1.0/pdcp.c b/openair2/LAYER2/PDCP_v10.1.0/pdcp.c
index 9adc6ecb5dfc90aa72926d3b233f281814261758..14a16f572b9ebf075719d2992df14c1d8cb7b4a1 100644
--- a/openair2/LAYER2/PDCP_v10.1.0/pdcp.c
+++ b/openair2/LAYER2/PDCP_v10.1.0/pdcp.c
@@ -134,7 +134,7 @@ notifiedFIFO_t         pdcp_sdu_list;
 pdcp_enb_t pdcp_enb[MAX_NUM_CCs];
 
 
-extern volatile int oai_exit;
+extern int oai_exit;
 
 pthread_t pdcp_stats_thread_desc;
 
diff --git a/openair2/NR_PHY_INTERFACE/NR_IF_Module.c b/openair2/NR_PHY_INTERFACE/NR_IF_Module.c
index 47705082d1c0b12fdd28a2321c401c9a8d43e322..f295d95184b84f8dcecfbe2c8a4897e8277f0505 100644
--- a/openair2/NR_PHY_INTERFACE/NR_IF_Module.c
+++ b/openair2/NR_PHY_INTERFACE/NR_IF_Module.c
@@ -265,23 +265,8 @@ static void free_unqueued_nfapi_indications(nfapi_nr_rach_indication_t *rach_ind
   }
   if (uci_ind && uci_ind->num_ucis > 0)
   {
-    for (int i = 0; i < uci_ind->num_ucis; i++) {
-      switch (uci_ind->uci_list[i].pdu_type) {
-        case NFAPI_NR_UCI_FORMAT_0_1_PDU_TYPE:
-          if (uci_ind->uci_list[i].pucch_pdu_format_0_1.harq) {
-            free_and_zero(uci_ind->uci_list[i].pucch_pdu_format_0_1.harq->harq_list);
-          }
-          free_and_zero(uci_ind->uci_list[i].pucch_pdu_format_0_1.harq);
-          free_and_zero(uci_ind->uci_list[i].pucch_pdu_format_0_1.sr);
-          break;
-
-        case NFAPI_NR_UCI_FORMAT_2_3_4_PDU_TYPE:
-          free_and_zero(uci_ind->uci_list[i].pucch_pdu_format_2_3_4.harq.harq_payload);
-          free_and_zero(uci_ind->uci_list[i].pucch_pdu_format_2_3_4.csi_part1.csi_part1_payload);
-          free_and_zero(uci_ind->uci_list[i].pucch_pdu_format_2_3_4.csi_part2.csi_part2_payload);
-          break;
-      }
-    }
+    /* PUCCH fields (HARQ, SR) are freed in handle_nr_uci_pucch_0_1() and
+     * handle_nr_uci_pucch_2_3_4() */
     free_and_zero(uci_ind->uci_list);
     free_and_zero(uci_ind);
   }
diff --git a/openair2/SDAP/nr_sdap/nr_sdap_entity.c b/openair2/SDAP/nr_sdap/nr_sdap_entity.c
index 5e3627296c56b62a15e3a33bdd390cb746913168..c1fcdced7966ce9dc5a3291a86af78359ee85279 100644
--- a/openair2/SDAP/nr_sdap/nr_sdap_entity.c
+++ b/openair2/SDAP/nr_sdap/nr_sdap_entity.c
@@ -197,18 +197,20 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
     }
 
     // Pushing SDAP SDU to GTP-U Layer
-    MessageDef *message_p;
-    uint8_t *gtpu_buffer_p;
-    gtpu_buffer_p = itti_malloc(TASK_PDCP_ENB, TASK_GTPV1_U, size + GTPU_HEADER_OVERHEAD_MAX - offset);
-    AssertFatal(gtpu_buffer_p != NULL, "OUT OF MEMORY");
-    memcpy(&gtpu_buffer_p[GTPU_HEADER_OVERHEAD_MAX], buf+offset, size-offset);
-    message_p = itti_alloc_new_message(TASK_PDCP_ENB, 0 , GTPV1U_GNB_TUNNEL_DATA_REQ);
+    MessageDef *message_p = itti_alloc_new_message_sized(TASK_PDCP_ENB,
+                                                         0,
+                                                         GTPV1U_GNB_TUNNEL_DATA_REQ,
+                                                         sizeof(gtpv1u_gnb_tunnel_data_req_t)
+                                                           + size + GTPU_HEADER_OVERHEAD_MAX - offset);
     AssertFatal(message_p != NULL, "OUT OF MEMORY");
-    GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).buffer = gtpu_buffer_p;
-    GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).length              = size-offset;
-    GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).offset              = GTPU_HEADER_OVERHEAD_MAX;
-    GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).rnti                = rnti;
-    GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).pdusession_id       = pdusession_id;
+    gtpv1u_gnb_tunnel_data_req_t *req = &GTPV1U_GNB_TUNNEL_DATA_REQ(message_p);
+    uint8_t *gtpu_buffer_p = (uint8_t *) (req + 1);
+    memcpy(gtpu_buffer_p + GTPU_HEADER_OVERHEAD_MAX, buf + offset, size - offset);
+    req->buffer        = gtpu_buffer_p;
+    req->length        = size - offset;
+    req->offset        = GTPU_HEADER_OVERHEAD_MAX;
+    req->rnti          = rnti;
+    req->pdusession_id = pdusession_id;
     LOG_D(SDAP, "%s()  sending message to gtp size %d\n", __func__,  size-offset);
     itti_send_msg_to_task(TASK_GTPV1_U, INSTANCE_DEFAULT, message_p);
   } else { //nrUE
diff --git a/openair3/ocp-gtpu/gtp_itf.cpp b/openair3/ocp-gtpu/gtp_itf.cpp
index dc93960e5e3e7bcd606f1769c599fc8075ae0ed1..afef8ea4357ed5ea291d7a635ae6b26f0f363b8a 100644
--- a/openair3/ocp-gtpu/gtp_itf.cpp
+++ b/openair3/ocp-gtpu/gtp_itf.cpp
@@ -142,6 +142,12 @@ class gtpEndPoints {
   pthread_mutex_t gtp_lock=PTHREAD_MUTEX_INITIALIZER;
   // the instance id will be the Linux socket handler, as this is uniq
   map<int, gtpEndPoint> instances;
+
+  ~gtpEndPoints() {
+    // automatically close all sockets on quit
+    for (const auto p : instances)
+      close(p.first);
+  }
 };
 
 gtpEndPoints globGtp;
diff --git a/targets/ARCH/COMMON/common_lib.h b/targets/ARCH/COMMON/common_lib.h
index 0f11447bf416da5476df5d257f26acc303d4b2bd..3434e40c87db5d3a748b8e8ff14a1d9c7fd0f85b 100644
--- a/targets/ARCH/COMMON/common_lib.h
+++ b/targets/ARCH/COMMON/common_lib.h
@@ -310,6 +310,8 @@ typedef struct {
   pthread_cond_t cond_write;
   /// mutex for trx write thread
   pthread_mutex_t mutex_write;
+  /// to inform the thread to exit
+  bool write_thread_exit;
 } openair0_thread_t;
 
 /*!\brief structure holds the parameters to configure USRP devices */
diff --git a/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.cpp b/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.cpp
index 7c0180e5e789e6cdb3a02a5a2eea91930d5dc869..f9cc4a111aecacb740dde74d2b1f93af53e683b2 100644
--- a/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.cpp
+++ b/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.cpp
@@ -309,6 +309,26 @@ static int trx_usrp_start(openair0_device *device) {
 
   return 0;
 }
+
+static void trx_usrp_send_end_of_burst(usrp_state_t *s) {
+  // if last packet sent was end of burst no need to do anything. otherwise send end of burst packet
+  if (s->tx_md.end_of_burst)
+    return;
+
+  s->tx_md.end_of_burst = true;
+  s->tx_md.start_of_burst = false;
+  s->tx_md.has_time_spec = false;
+
+  int32_t dummy = 0;
+  std::vector<const void *> buffs;
+  for (size_t ch = 0; ch < s->tx_stream->get_num_channels(); ch++)
+    buffs.push_back(&dummy); // same buffer for each channel
+
+  s->tx_stream->send(buffs, 0, s->tx_md);
+}
+
+static void trx_usrp_write_reset(openair0_thread_t *wt);
+
 /*! \brief Terminate operation of the USRP transceiver -- free all associated resources
  * \param device the hardware to use
  */
@@ -318,11 +338,27 @@ static void trx_usrp_end(openair0_device *device) {
 
   usrp_state_t *s = (usrp_state_t *)device->priv;
 
-  if (s == NULL)
-    return;
+  AssertFatal(s != NULL, "%s() called on uninitialized USRP\n", __func__);
   iqrecorder_end(device);
 
-
+  LOG_I(HW, "releasing USRP\n");
+  if (usrp_tx_thread != 0)
+    trx_usrp_write_reset(&device->write_thread);
+
+  trx_usrp_send_end_of_burst(s);
+  s->tx_stream->~tx_streamer();
+  s->rx_stream->~rx_streamer();
+  s->usrp->~multi_usrp();
+  free(s);
+  device->priv = NULL;
+  device->trx_start_func = NULL;
+  device->trx_get_stats_func = NULL;
+  device->trx_reset_stats_func = NULL;
+  device->trx_end_func = NULL;
+  device->trx_stop_func = NULL;
+  device->trx_set_freq_func = NULL;
+  device->trx_set_gains_func = NULL;
+  device->trx_write_init = NULL;
 }
 
 /*! \brief Called to send samples to the USRP RF target
@@ -505,6 +541,8 @@ void *trx_usrp_write_thread(void * arg){
     while (write_thread->count_write == 0) {
       pthread_cond_wait(&write_thread->cond_write,&write_thread->mutex_write); // this unlocks mutex_rxtx while waiting and then locks it again
     }
+    if (write_thread->write_thread_exit)
+      break;
     VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_WRITE_THREAD, 1 );
     s = (usrp_state_t *)device->priv;
     start = write_thread->start;
@@ -599,6 +637,7 @@ int trx_usrp_write_init(openair0_device *device){
   write_thread->start              = 0;
   write_thread->end                = 0;
   write_thread->count_write        = 0;
+  write_thread->write_thread_exit  = false;
   printf("end of tx write thread\n");
   pthread_mutex_init(&write_thread->mutex_write, NULL);
   pthread_cond_init(&write_thread->cond_write, NULL);
@@ -607,6 +646,17 @@ int trx_usrp_write_init(openair0_device *device){
   return(0);
 }
 
+static void trx_usrp_write_reset(openair0_thread_t *wt) {
+  pthread_mutex_lock(&wt->mutex_write);
+  wt->count_write = 1;
+  wt->write_thread_exit = true;
+  pthread_mutex_unlock(&wt->mutex_write);
+  pthread_cond_signal(&wt->cond_write);
+  void *retval = NULL;
+  pthread_join(wt->pthread_write, &retval);
+  LOG_I(HW, "stopped USRP write thread\n");
+}
+
 //---------------------end-------------------------
 
 /*! \brief Receive samples from hardware.
diff --git a/targets/ARCH/rfsimulator/simulator.c b/targets/ARCH/rfsimulator/simulator.c
index 2cb003680414688f8da661435537ed8007c7dcc7..33d0179d8d2f5785119e5ee1eb3849cbb9a859a4 100644
--- a/targets/ARCH/rfsimulator/simulator.c
+++ b/targets/ARCH/rfsimulator/simulator.c
@@ -225,15 +225,16 @@ enum  blocking_t {
 };
 
 static void setblocking(int sock, enum blocking_t active) {
-  int opts;
-  AssertFatal( (opts = fcntl(sock, F_GETFL)) >= 0,"");
+  int opts = fcntl(sock, F_GETFL);
+  AssertFatal(opts >= 0, "fcntl(): errno %d, %s\n", errno, strerror(errno));
 
   if (active==blocking)
     opts = opts & ~O_NONBLOCK;
   else
     opts = opts | O_NONBLOCK;
 
-  AssertFatal(fcntl(sock, F_SETFL, opts) >= 0, "");
+  opts = fcntl(sock, F_SETFL, opts);
+  AssertFatal(opts >= 0, "fcntl(): errno %d, %s\n", errno, strerror(errno));
 }
 
 static bool flushInput(rfsimulator_state_t *t, int timeout, int nsamps);
@@ -248,7 +249,6 @@ static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t)
               "Bug: %d/%p/%zd/%p", fd, _buf, count, t);
   char *buf = _buf;
   ssize_t l;
-  setblocking(fd, notBlocking);
 
   while (count) {
     l = write(fd, buf, count);
@@ -740,7 +740,15 @@ static int rfsimulator_get_stats(openair0_device *device) {
 static int rfsimulator_reset_stats(openair0_device *device) {
   return 0;
 }
-static void rfsimulator_end(openair0_device *device) {}
+static void rfsimulator_end(openair0_device *device) {
+  rfsimulator_state_t* s = device->priv;
+  for (int i = 0; i < FD_SETSIZE; i++) {
+    buffer_t *b = &s->buf[i];
+    if (b->conn_sock >= 0 )
+      close(b->conn_sock);
+  }
+  close(s->epollfd);
+}
 static int rfsimulator_stop(openair0_device *device) {
   return 0;
 }
diff --git a/targets/RT/USER/lte-enb.c b/targets/RT/USER/lte-enb.c
index 3d62d5c19ad5928afcee0719f71c23c77486bb98..b99d97a180644efd26ed6f54f8ee13669e5a2c6f 100644
--- a/targets/RT/USER/lte-enb.c
+++ b/targets/RT/USER/lte-enb.c
@@ -106,7 +106,7 @@ struct timing_info_t {
 // Fix per CC openair rf/if device update
 // extern openair0_device openair0;
 
-extern volatile int oai_exit;
+extern int oai_exit;
 
 extern int transmission_mode;
 
diff --git a/targets/RT/USER/lte-ru.c b/targets/RT/USER/lte-ru.c
index 1d464cb90c4b66433bb9ee48fce9dd4e438c5a3b..859a5c40a2964fe5472506af4fadb2828dd571ec 100644
--- a/targets/RT/USER/lte-ru.c
+++ b/targets/RT/USER/lte-ru.c
@@ -78,7 +78,7 @@ static int DEFBFW[] = {0x00007fff};
 
 #define MBMS_EXPERIMENTAL
 
-extern volatile int oai_exit;
+extern int oai_exit;
 extern clock_source_t clock_source;
 #include "executables/thread-common.h"
 //extern PARALLEL_CONF_t get_thread_parallel_conf(void);
diff --git a/targets/RT/USER/lte-softmodem.c b/targets/RT/USER/lte-softmodem.c
index 5d17cc17bbac079de15122ef0d0591bd114bbecc..b1275e5dd5b9a1d30fe602cc3b036033f232e33c 100644
--- a/targets/RT/USER/lte-softmodem.c
+++ b/targets/RT/USER/lte-softmodem.c
@@ -106,7 +106,7 @@ uint16_t runtime_phy_rx[29][6]; // SISO [MCS 0-28][RBs 0-5 : 6, 15, 25, 50, 75,
 uint16_t runtime_phy_tx[29][6]; // SISO [MCS 0-28][RBs 0-5 : 6, 15, 25, 50, 75, 100]
 
 
-volatile int             oai_exit = 0;
+int oai_exit = 0;
 
 uint64_t                 downlink_frequency[MAX_NUM_CCs][4];
 int32_t                  uplink_frequency_offset[MAX_NUM_CCs][4];
diff --git a/targets/RT/USER/lte-softmodem.h b/targets/RT/USER/lte-softmodem.h
index 9493d2cda38f6a05644d9790715d0cbc16ecdff9..e1fa279195c0c2c7d4d52e7902e2156611ab54e2 100644
--- a/targets/RT/USER/lte-softmodem.h
+++ b/targets/RT/USER/lte-softmodem.h
@@ -146,7 +146,7 @@ extern int rx_input_level_dBm;
 extern uint64_t num_missed_slots; // counter for the number of missed slots
 
 extern int oaisim_flag;
-extern volatile int oai_exit;
+extern int oai_exit;
 
 extern openair0_config_t openair0_cfg[MAX_CARDS];
 extern pthread_cond_t sync_cond;
diff --git a/targets/RT/USER/lte-uesoftmodem.c b/targets/RT/USER/lte-uesoftmodem.c
index 05602b63da6ce068a4a02397b429a35d87d192d7..963044a506f100c1c49ea7530b442fe0441b0f99 100644
--- a/targets/RT/USER/lte-uesoftmodem.c
+++ b/targets/RT/USER/lte-uesoftmodem.c
@@ -99,7 +99,7 @@ int config_sync_var=-1;
 uint16_t runtime_phy_rx[29][6]; // SISO [MCS 0-28][RBs 0-5 : 6, 15, 25, 50, 75, 100]
 uint16_t runtime_phy_tx[29][6]; // SISO [MCS 0-28][RBs 0-5 : 6, 15, 25, 50, 75, 100]
 
-volatile int             oai_exit = 0;
+int oai_exit = 0;
 
 unsigned int                    mmapped_dma=0;