Index: libcfa/src/concurrency/io.cfa
===================================================================
--- libcfa/src/concurrency/io.cfa	(revision 1ab773e094a82622dc011dcb029d58e708e06f48)
+++ libcfa/src/concurrency/io.cfa	(revision a7662b89ef56518a9c56ecec0e12c801276f2814)
@@ -85,5 +85,5 @@
 	static io_context$ * __ioarbiter_allocate( io_arbiter$ & this, __u32 idxs[], __u32 want );
 	static void __ioarbiter_submit( io_context$ * , __u32 idxs[], __u32 have, bool lazy );
-	static void __ioarbiter_flush ( io_context$ & );
+	static void __ioarbiter_flush ( io_context$ &, bool kernel );
 	static inline void __ioarbiter_notify( io_context$ & ctx );
 //=============================================================================================
@@ -94,8 +94,18 @@
 	extern void __kernel_unpark( thread$ * thrd, unpark_hint );
 
+	static inline void __post(oneshot & this, bool kernel, unpark_hint hint) {
+		thread$ * t = post( this, false );
+		if(kernel) __kernel_unpark( t, hint );
+		else unpark( t, hint );
+	}
+
+	// actual system call of io uring
+	// wrap so everything that needs to happen around it is always done
+	//   i.e., stats, book keeping, sqe reclamation, etc.
 	static void ioring_syscsll( struct io_context$ & ctx, unsigned int min_comp, unsigned int flags ) {
 		__STATS__( true, io.calls.flush++; )
 		int ret;
 		for() {
+			// do the system call in a loop, repeat on interrupts
 			ret = syscall( __NR_io_uring_enter, ctx.fd, ctx.sq.to_submit, min_comp, flags, (sigset_t *)0p, _NSIG / 8);
 			if( ret < 0 ) {
@@ -120,5 +130,6 @@
 		/* paranoid */ verify( ctx.sq.to_submit >= ret );
 
-		ctx.sq.to_submit -= ret;
+		// keep track of how many still need submitting
+		__atomic_fetch_sub(&ctx.sq.to_submit, ret, __ATOMIC_SEQ_CST);
 
 		/* paranoid */ verify( ctx.sq.to_submit <= *ctx.sq.num );
@@ -129,7 +140,9 @@
 		/* paranoid */ verify( ! __preemption_enabled() );
 
+		// mark that there is no pending io left
 		__atomic_store_n(&ctx.proc->io.pending, false, __ATOMIC_RELAXED);
 	}
 
+	// try to acquire an io context for draining, helping means we never *need* to drain, we can always do it later
 	static bool try_acquire( io_context$ * ctx ) __attribute__((nonnull(1))) {
 		/* paranoid */ verify( ! __preemption_enabled() );
@@ -138,4 +151,5 @@
 
 		{
+			// if there is nothing to drain there is no point in acquiring anything
 			const __u32 head = *ctx->cq.head;
 			const __u32 tail = *ctx->cq.tail;
@@ -144,18 +158,22 @@
 		}
 
-		// Drain the queue
-		if(!__atomic_try_acquire(&ctx->cq.lock)) {
+		// try a simple spinlock acquire, it's likely there are completions to drain
+		if(!__atomic_try_acquire(&ctx->cq.try_lock)) {
+			// some other processor already has it
 			__STATS__( false, io.calls.locked++; )
 			return false;
 		}
 
+		// acquired!!
 		return true;
 	}
 
+	// actually drain the completion
 	static bool __cfa_do_drain( io_context$ * ctx, cluster * cltr ) __attribute__((nonnull(1, 2))) {
 		/* paranoid */ verify( ! __preemption_enabled() );
 		/* paranoid */ verify( ready_schedule_islocked() );
-		/* paranoid */ verify( ctx->cq.lock == true );
-
+		/* paranoid */ verify( ctx->cq.try_lock == true );
+
+		// get all the invariants and initial state
 		const __u32 mask = *ctx->cq.mask;
 		const __u32 num  = *ctx->cq.num;
@@ -166,4 +184,5 @@
 		for() {
 			// re-read the head and tail in case it already changed.
+			// count the difference between the two
 			const __u32 head = *ctx->cq.head;
 			const __u32 tail = *ctx->cq.tail;
@@ -171,4 +190,5 @@
 			__STATS__( false, io.calls.drain++; io.calls.completed += count; )
 
+			// for everything between head and tail, drain it
 			for(i; count) {
 				unsigned idx = (head + i) & mask;
@@ -177,10 +197,14 @@
 				/* paranoid */ verify(&cqe);
 
+				// find the future in the completion
 				struct io_future_t * future = (struct io_future_t *)(uintptr_t)cqe.user_data;
 				// __cfadbg_print_safe( io, "Kernel I/O : Syscall completed : cqe %p, result %d for %p\n", &cqe, cqe.res, future );
 
+				// don't directly fulfill the future, preemption is disabled so we need to use kernel_unpark
 				__kernel_unpark( fulfil( *future, cqe.res, false ), UNPARK_LOCAL );
 			}
 
+			// update the timestamps accordingly
+			// keep a local copy so we can update the relaxed copy
 			ts_next = ctx->cq.ts = rdtscl();
 
@@ -190,6 +214,8 @@
 			ctx->proc->idle_wctx.drain_time = ts_next;
 
+			// we finished draining the completions... unless the ring buffer was full and there are more secret completions in the kernel.
 			if(likely(count < num)) break;
 
+			// the ring buffer was full, there could be more stuff in the kernel.
 			ioring_syscsll( *ctx, 0, IORING_ENTER_GETEVENTS);
 		}
@@ -199,6 +225,8 @@
 		/* paranoid */ verify( ! __preemption_enabled() );
 
-		__atomic_unlock(&ctx->cq.lock);
-
+		// everything is drained, we can release the lock
+		__atomic_unlock(&ctx->cq.try_lock);
+
+		// update the relaxed timestamp
 		touch_tsc( cltr->sched.io.tscs, ctx->cq.id, ts_prev, ts_next, false );
 
@@ -206,8 +234,26 @@
 	}
 
+	// call from a processor to flush
+	// contains all the bookkeeping a proc must do, not just the barebones flushing logic
+	void __cfa_do_flush( io_context$ & ctx, bool kernel ) {
+		/* paranoid */ verify( ! __preemption_enabled() );
+
+		// flush any external requests
+		ctx.sq.last_external = false; // clear the external bit, the arbiter will reset it if needed
+		__ioarbiter_flush( ctx, kernel );
+
+		// if submitting must be submitted, do the system call
+		if(ctx.sq.to_submit != 0) {
+			ioring_syscsll(ctx, 0, 0);
+		}
+	}
+
+	// call from a processor to drain
+	// contains all the bookkeeping a proc must do, not just the barebones draining logic
 	bool __cfa_io_drain( struct processor * proc ) {
 		bool local = false;
 		bool remote = false;
 
+		// make sure no ones creates/destroys io contexts
 		ready_schedule_lock();
 
@@ -217,4 +263,5 @@
 		/* paranoid */ verify( ctx );
 
+		// Help if needed
 		with(cltr->sched) {
 			const size_t ctxs_count = io.count;
@@ -230,9 +277,13 @@
 			const unsigned long long ctsc = rdtscl();
 
+			// only help once every other time
+			// pick a target when not helping
 			if(proc->io.target == UINT_MAX) {
 				uint64_t chaos = __tls_rand();
+				// choose who to help and whether to accept helping far processors 
 				unsigned ext = chaos & 0xff;
 				unsigned other  = (chaos >> 8) % (ctxs_count);
 
+				// if the processor is on the same cache line or is lucky ( 3 out of 256 odds ) help it
 				if(ext < 3 || __atomic_load_n(&caches[other / __shard_factor.io].id, __ATOMIC_RELAXED) == this_cache) {
 					proc->io.target = other;
@@ -240,23 +291,34 @@
 			}
 			else {
+				// a target was picked last time, help it
 				const unsigned target = proc->io.target;
 				/* paranoid */ verify( io.tscs[target].t.tv != ULLONG_MAX );
+				// make sure the target hasn't stopped existing since last time
 				HELP: if(target < ctxs_count) {
+					// calculate it's age and how young it could be before we give ip on helping
 					const __readyQ_avg_t cutoff = calc_cutoff(ctsc, ctx->cq.id, ctxs_count, io.data, io.tscs, __shard_factor.io, false);
 					const __readyQ_avg_t age = moving_average(ctsc, io.tscs[target].t.tv, io.tscs[target].t.ma, false);
 					__cfadbg_print_safe(io, "Kernel I/O: Help attempt on %u from %u, age %'llu vs cutoff %'llu, %s\n", target, ctx->cq.id, age, cutoff, age > cutoff ? "yes" : "no");
+					// is the target older than the cutoff, recall 0 is oldest and bigger ints are younger
 					if(age <= cutoff) break HELP;
 
-					if(!try_acquire(io.data[target])) break HELP;
-
+					// attempt to help the submission side
+					__cfa_do_flush( *io.data[target], true );
+
+					// attempt to help the completion side
+					if(!try_acquire(io.data[target])) break HELP; // already acquire no help needed
+
+					// actually help
 					if(!__cfa_do_drain( io.data[target], cltr )) break HELP;
 
+					// track we did help someone
 					remote = true;
 					__STATS__( true, io.calls.helped++; )
 				}
+
+				// reset the target
 				proc->io.target = UINT_MAX;
 			}
 		}
-
 
 		// Drain the local queue
@@ -270,7 +332,13 @@
 
 		ready_schedule_unlock();
+
+		// return true if some completion entry, local or remote, was drained
 		return local || remote;
 	}
 
+
+
+	// call from a processor to flush
+	// contains all the bookkeeping a proc must do, not just the barebones flushing logic
 	bool __cfa_io_flush( struct processor * proc ) {
 		/* paranoid */ verify( ! __preemption_enabled() );
@@ -278,13 +346,7 @@
 		/* paranoid */ verify( proc->io.ctx );
 
-		io_context$ & ctx = *proc->io.ctx;
-
-		__ioarbiter_flush( ctx );
-
-		if(ctx.sq.to_submit != 0) {
-			ioring_syscsll(ctx, 0, 0);
-
-		}
-
+		__cfa_do_flush( *proc->io.ctx, false );
+
+		// also drain since some stuff will immediately complete
 		return __cfa_io_drain( proc );
 	}
@@ -393,5 +455,8 @@
 	//=============================================================================================
 	// submission
-	static inline void __submit_only( struct io_context$ * ctx, __u32 idxs[], __u32 have) {
+	// barebones logic to submit a group of sqes
+	static inline void __submit_only( struct io_context$ * ctx, __u32 idxs[], __u32 have, bool lock) {
+		if(!lock) 
+			lock( ctx->ext_sq.lock __cfaabi_dbg_ctx2 );
 		// We can proceed to the fast path
 		// Get the right objects
@@ -408,13 +473,18 @@
 		// Make the sqes visible to the submitter
 		__atomic_store_n(sq.kring.tail, tail + have, __ATOMIC_RELEASE);
-		sq.to_submit += have;
-
+		__atomic_fetch_add(&sq.to_submit, have, __ATOMIC_SEQ_CST);
+
+		// set the bit to mark things need to be flushed
 		__atomic_store_n(&ctx->proc->io.pending, true, __ATOMIC_RELAXED);
 		__atomic_store_n(&ctx->proc->io.dirty  , true, __ATOMIC_RELAXED);
-	}
-
+
+		if(!lock) 
+			unlock( ctx->ext_sq.lock );
+	}
+
+	// submission logic + maybe flushing
 	static inline void __submit( struct io_context$ * ctx, __u32 idxs[], __u32 have, bool lazy) {
 		__sub_ring_t & sq = ctx->sq;
-		__submit_only(ctx, idxs, have);
+		__submit_only(ctx, idxs, have, false);
 
 		if(sq.to_submit > 30) {
@@ -428,4 +498,6 @@
 	}
 
+	// call from a processor to flush
+	// might require arbitration if the thread was migrated after the allocation
 	void cfa_io_submit( struct io_context$ * inctx, __u32 idxs[], __u32 have, bool lazy ) __attribute__((nonnull (1))) libcfa_public {
 		// __cfadbg_print_safe(io, "Kernel I/O : attempting to submit %u (%s)\n", have, lazy ? "lazy" : "eager");
@@ -441,4 +513,5 @@
 		if( ctx == inctx )		// We have the right instance?
 		{
+			// yes! fast submit
 			__submit(ctx, idxs, have, lazy);
 
@@ -507,4 +580,5 @@
 		__atomic_store_n(&ctx.sq.free_ring.tail, ftail + count, __ATOMIC_SEQ_CST);
 
+		// notify the allocator that new allocations can be made
 		__ioarbiter_notify(ctx);
 
@@ -557,25 +631,37 @@
 	}
 
+	// notify the arbiter that new allocations are available
 	static void __ioarbiter_notify( io_arbiter$ & this, io_context$ * ctx ) {
 		/* paranoid */ verify( !empty(this.pending.queue) );
-
+		/* paranoid */ verify( __preemption_enabled() );
+
+		// mutual exclusion is needed
 		lock( this.pending.lock __cfaabi_dbg_ctx2 );
 		{
+			__cfadbg_print_safe(io, "Kernel I/O : notifying\n");
+
+			// as long as there are pending allocations try to satisfy them
+			// for simplicity do it in FIFO order
 			while( !empty(this.pending.queue) ) {
-				__cfadbg_print_safe(io, "Kernel I/O : notifying\n");
+				// get first pending allocs
 				__u32 have = ctx->sq.free_ring.tail - ctx->sq.free_ring.head;
 				__pending_alloc & pa = (__pending_alloc&)head( this.pending.queue );
 
+				// check if we have enough to satisfy the request
 				if( have > pa.want ) goto DONE;
+
+				// if there are enough allocations it means we can drop the request
 				drop( this.pending.queue );
 
 				/* paranoid */__attribute__((unused)) bool ret =
 
+				// actually do the alloc
 				__alloc(ctx, pa.idxs, pa.want);
 
 				/* paranoid */ verify( ret );
 
+				// write out which context statisfied the request and post
+				// this 
 				pa.ctx = ctx;
-
 				post( pa.waitctx );
 			}
@@ -585,13 +671,15 @@
 		}
 		unlock( this.pending.lock );
-	}
-
+
+		/* paranoid */ verify( __preemption_enabled() );
+	}
+
+	// short hand to avoid the mutual exclusion of the pending is empty regardless
 	static void __ioarbiter_notify( io_context$ & ctx ) {
-		if(!empty( ctx.arbiter->pending )) {
-			__ioarbiter_notify( *ctx.arbiter, &ctx );
-		}
-	}
-
-	// Simply append to the pending
+		if(empty( ctx.arbiter->pending )) return;
+		__ioarbiter_notify( *ctx.arbiter, &ctx );
+	}
+
+	// Submit from outside the local processor: append to the outstanding list
 	static void __ioarbiter_submit( io_context$ * ctx, __u32 idxs[], __u32 have, bool lazy ) {
 		__cfadbg_print_safe(io, "Kernel I/O : submitting %u from the arbiter to context %u\n", have, ctx->fd);
@@ -599,4 +687,5 @@
 		__cfadbg_print_safe(io, "Kernel I/O : waiting to submit %u\n", have);
 
+		// create the intrusive object to append
 		__external_io ei;
 		ei.idxs = idxs;
@@ -604,13 +693,20 @@
 		ei.lazy = lazy;
 
+		// enqueue the io
 		bool we = enqueue(ctx->ext_sq, (__outstanding_io&)ei);
 
+		// mark pending
 		__atomic_store_n(&ctx->proc->io.pending, true, __ATOMIC_SEQ_CST);
 
+		// if this is the first to be enqueued, signal the processor in an attempt to speed up flushing
+		// if it's not the first enqueue, a signal is already in transit
 		if( we ) {
 			sigval_t value = { PREEMPT_IO };
 			__cfaabi_pthread_sigqueue(ctx->proc->kernel_thread, SIGUSR1, value);
-		}
-
+			__STATS__( false, io.flush.signal += 1; )
+		}
+		__STATS__( false, io.submit.extr += 1; )
+
+		// to avoid dynamic allocation/memory reclamation headaches, wait for it to have been submitted
 		wait( ei.waitctx );
 
@@ -618,23 +714,53 @@
 	}
 
-	static void __ioarbiter_flush( io_context$ & ctx ) {
-		if(!empty( ctx.ext_sq )) {
-			__STATS__( false, io.flush.external += 1; )
-
-			__cfadbg_print_safe(io, "Kernel I/O : arbiter flushing\n");
-
-			lock( ctx.ext_sq.lock __cfaabi_dbg_ctx2 );
-			{
-				while( !empty(ctx.ext_sq.queue) ) {
-					__external_io & ei = (__external_io&)drop( ctx.ext_sq.queue );
-
-					__submit_only(&ctx, ei.idxs, ei.have);
-
-					post( ei.waitctx );
-				}
-
-				ctx.ext_sq.empty = true;
+	// flush the io arbiter: move all external io operations to the submission ring
+	static void __ioarbiter_flush( io_context$ & ctx, bool kernel ) {
+		// if there are no external operations just return
+		if(empty( ctx.ext_sq )) return;
+
+		// stats and logs
+		__STATS__( false, io.flush.external += 1; )
+		__cfadbg_print_safe(io, "Kernel I/O : arbiter flushing\n");
+
+		// this can happen from multiple processors, mutual exclusion is needed
+		lock( ctx.ext_sq.lock __cfaabi_dbg_ctx2 );
+		{
+			// pop each operation one at a time.
+			// There is no wait morphing because of the io sq ring
+			while( !empty(ctx.ext_sq.queue) ) {
+				// drop the element from the queue
+				__external_io & ei = (__external_io&)drop( ctx.ext_sq.queue );
+
+				// submit it
+				__submit_only(&ctx, ei.idxs, ei.have, true);
+
+				// wake the thread that was waiting on it
+				// since this can both be called from kernel and user, check the flag before posting
+				__post( ei.waitctx, kernel, UNPARK_LOCAL );
 			}
-			unlock(ctx.ext_sq.lock );
+
+			// mark the queue as empty
+			ctx.ext_sq.empty = true;
+			ctx.sq.last_external = true;
+		}
+		unlock(ctx.ext_sq.lock );
+	}
+
+	extern "C" {
+		// debug functions used for gdb
+		// io_uring doesn't yet support gdb soe the kernel-shared data structures aren't viewable in gdb
+		// these functions read the data that gdb can't and should be removed once the support is added
+		static __u32 __cfagdb_cq_head( io_context$ * ctx ) __attribute__((nonnull(1),used,noinline)) { return *ctx->cq.head; }
+		static __u32 __cfagdb_cq_tail( io_context$ * ctx ) __attribute__((nonnull(1),used,noinline)) { return *ctx->cq.tail; }
+		static __u32 __cfagdb_cq_mask( io_context$ * ctx ) __attribute__((nonnull(1),used,noinline)) { return *ctx->cq.mask; }
+		static __u32 __cfagdb_sq_head( io_context$ * ctx ) __attribute__((nonnull(1),used,noinline)) { return *ctx->sq.kring.head; }
+		static __u32 __cfagdb_sq_tail( io_context$ * ctx ) __attribute__((nonnull(1),used,noinline)) { return *ctx->sq.kring.tail; }
+		static __u32 __cfagdb_sq_mask( io_context$ * ctx ) __attribute__((nonnull(1),used,noinline)) { return *ctx->sq.mask; }
+
+		// fancier version that reads an sqe and copies it out.
+		static struct io_uring_sqe __cfagdb_sq_at( io_context$ * ctx, __u32 at ) __attribute__((nonnull(1),used,noinline)) {
+			__u32 ax = at & *ctx->sq.mask;
+			__u32 ix = ctx->sq.kring.array[ax];
+			return ctx->sq.sqes[ix];
 		}
 	}
Index: libcfa/src/concurrency/io/setup.cfa
===================================================================
--- libcfa/src/concurrency/io/setup.cfa	(revision 1ab773e094a82622dc011dcb029d58e708e06f48)
+++ libcfa/src/concurrency/io/setup.cfa	(revision a7662b89ef56518a9c56ecec0e12c801276f2814)
@@ -216,5 +216,5 @@
 
 		// completion queue
-		cq.lock      = false;
+		cq.try_lock  = false;
 		cq.id        = MAX;
 		cq.ts        = rdtscl();
Index: libcfa/src/concurrency/io/types.hfa
===================================================================
--- libcfa/src/concurrency/io/types.hfa	(revision 1ab773e094a82622dc011dcb029d58e708e06f48)
+++ libcfa/src/concurrency/io/types.hfa	(revision a7662b89ef56518a9c56ecec0e12c801276f2814)
@@ -37,5 +37,9 @@
 	//-----------------------------------------------------------------------
 	// Ring Data structure
-      struct __sub_ring_t {
+	// represent the io_uring submission ring which contains operations that will be sent to io_uring for processing
+	struct __sub_ring_t {
+		// lock needed because remote processors might need to flush the instance 
+		__spinlock_t lock;
+
 		struct {
 			// Head and tail of the ring (associated with array)
@@ -58,5 +62,5 @@
 
 		// number of sqes to submit on next system call.
-		__u32 to_submit;
+		volatile __u32 to_submit;
 
 		// number of entries and mask to go with it
@@ -77,11 +81,18 @@
 		void * ring_ptr;
 		size_t ring_sz;
-	};
-
+
+		// for debug purposes, whether or not the last flush was due to a arbiter flush
+		bool last_external;
+	};
+
+	// represent the io_uring completion ring which contains operations that have completed
 	struct __cmp_ring_t {
-		volatile bool lock;
-
+		// needed because remote processors can help drain the buffer
+		volatile bool try_lock;
+
+		// id of the ring, used for the helping/topology algorithms
 		unsigned id;
 
+		// timestamp from last time it was drained
 		unsigned long long ts;
 
@@ -105,40 +116,69 @@
 	};
 
+	// struct representing an io operation that still needs processing
+	// actual operations are expected to inherit from this
 	struct __outstanding_io {
+		// intrusive link fields
 		inline Colable;
+
+		// primitive on which to block until the io is processed
 		oneshot waitctx;
 	};
 	static inline __outstanding_io *& Next( __outstanding_io * n ) { return (__outstanding_io *)Next( (Colable *)n ); }
 
+	// queue of operations that are outstanding
 	struct __outstanding_io_queue {
+		// spinlock for protection
+		// TODO: changing to a lock that blocks, I haven't examined whether it should be a kernel or user lock
 		__spinlock_t lock;
+
+		// the actual queue
 		Queue(__outstanding_io) queue;
+
+		// volatile used to avoid the need for taking the lock if it's empty
 		volatile bool empty;
 	};
 
+	// struct representing an operation that was submitted 
 	struct __external_io {
+		// inherits from outstanding io
 		inline __outstanding_io;
+
+		// pointer and count to an array of ids to be submitted
 		__u32 * idxs;
 		__u32 have;
+
+		// whether or not these can be accumulated before flushing the buffer
 		bool lazy;
 	};
 
-
+	// complete io_context, contains all the data for io submission and completion
 	struct __attribute__((aligned(64))) io_context$ {
+		// arbiter, used in cases where threads for migrated at unfortunate moments
 		io_arbiter$ * arbiter;
+
+		// which prcessor the context is tied to
 		struct processor * proc;
 
+		// queue of io submissions that haven't beeen processed.
 		__outstanding_io_queue ext_sq;
 
+		// io_uring ring data structures
 		struct __sub_ring_t sq;
 		struct __cmp_ring_t cq;
+
+		// flag the io_uring rings where created with
 		__u32 ring_flags;
+
+		// file descriptor that identifies the io_uring instance
 		int fd;
 	};
 
+	// short hand to check when the io_context was last processed (io drained)
 	static inline unsigned long long ts(io_context$ *& this) {
 		const __u32 head = *this->cq.head;
 		const __u32 tail = *this->cq.tail;
 
+		// if there is no pending completions, just pretend it's infinetely recent
 		if(head == tail) return ULLONG_MAX;
 
@@ -146,12 +186,20 @@
 	}
 
+	// structure represeting allocations that couldn't succeed locally
 	struct __pending_alloc {
+		// inherit from outstanding io
 		inline __outstanding_io;
+
+		// array and size of the desired allocation
 		__u32 * idxs;
 		__u32 want;
+
+		// output param, the context the io was allocated from 
 		io_context$ * ctx;
 	};
 
+	// arbiter that handles cases where the context tied to the local processor is unable to satisfy the io
 	monitor __attribute__((aligned(64))) io_arbiter$ {
+		// contains a queue of io for pending allocations
 		__outstanding_io_queue pending;
 	};
Index: libcfa/src/concurrency/kernel.cfa
===================================================================
--- libcfa/src/concurrency/kernel.cfa	(revision 1ab773e094a82622dc011dcb029d58e708e06f48)
+++ libcfa/src/concurrency/kernel.cfa	(revision a7662b89ef56518a9c56ecec0e12c801276f2814)
@@ -258,4 +258,7 @@
 		__cfadbg_print_safe(runtime_core, "Kernel : core %p stopping\n", this);
 	}
+
+	__cfa_io_flush( this );
+	__cfa_io_drain( this );
 
 	post( this->terminated );
