/*
 * Copyright 2018-2024, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */
/*
Overview:
	The C-style API calls for semaphore/port/thread/etc generally forward/delegate
	to this C++ object. The implementation is very straightforward in most cases:
	Haiku semaphores are mapped to a Genode::Semaphore each, haiku threads
	are mapped to a pthread, and so on.
	Ports are also implemented in a straightforward manner in the _local_ case,
	though accessing ports located in a _remote_ component is more involved, and
	indirects through a "broker" component that connects the source and recipient
	(that's handled in api-public...cpp, leaving this file untouched, though)
	-- Notes on the Naming: --
	The name "kind-of-server-side" has NOT aged well in 2022, now that I have actual
	server-side 'kernel' services, in the form of the Broker component, showing
	that _this_ file is diametrically opposed (fully client-side!)

Aliases:
	- core (might be a better term than "kernel" since we of course live in user space...)
*/


// genode/libc
#include <libc/component.h>  // Libc::with_libc()

// libc.lib.so
#include <pthread.h>

// this
#include "kinda_serverside.h"

// -namespaces-
using Genode::Semaphore;



/**************/
static int debug = 0;
static int debug_thr = 0;  // sensible values: 0, 1, 3, 5, 6, 8, 9

// global
/***************/
hog::HaiCalls_Srv
	haiku_syscalls_srv_;




//#pragma mark - Internal/Private -

//#pragma mark - kernel, server side -


hog::HaiCalls_Srv::HaiCalls_Srv()
:	kernelLock()
	,nextSemaphore( 0 )
	,semMap()
	,portQueues()
	,nextThread( 1 )  // start numbering spawn_thread() threads at 1 ...for debugging aesthetics
	,threadsMap()
{
	//Genode::log( "haiku pseudo kernel: ctor" );
}


///later: _DTor_, which will delete portQueues items and threadsMap items...



//#pragma mark - sem -


sem_id hog::HaiCalls_Srv::SemCreate( int32 initial_count, const char *)// name )
{
	MTSAFE_GUARD;
	
	sem_id sem = nextSemaphore++;  // !
	semMap[sem] = new Semaphore( initial_count );
	
	MTSAFE_GUARD_Epilogue;
	
	return sem;
}

status_t hog::HaiCalls_Srv::SemDelete( sem_id id )
{
	MTSAFE_GUARD;
	
	auto it =
		semMap.find( id );
	
	if( semMap.end() == it )
		//
		return B_BAD_SEM_ID;
	
	if( debug_thr >= 2 )
		Genode::log( __func__, " <", id, ">" );
	
	// This up() is required for async-mode BAlert.Go() to ever return:
	it->second->up();
#if 1
	delete it->second;  // delete Genode::Semaphore (does *not* seem to entail an up(), hence the above call to up() to force it)
	semMap.erase( it );
#else
	move_to_purgatory();
#endif
	
	MTSAFE_GUARD_Epilogue;
	
	return B_OK;
}


status_t hog::HaiCalls_Srv::SemAcquire( sem_id id )
{
#if 0
	MTSAFE_GUARD;
	
	auto it =
		semMap.find( id );
	
	if( semMap.end() == it )
		//
		return B_BAD_SEM_ID;
	
	//Genode::log( "    SemAcquire ", id, " .... cnt=", it->second->cnt() );
	const auto old = it->second->cnt();
	
Commented out/replaced -- that was not good enough, in fact it was a terrible way to do it ! :
	if( it->second->cnt() <= 0 )
		//
		return EAGAIN;  // note: EAGAIN maps to B_WOULD_BLOCK, hmmmmm
	//
	// else: >= 1:
	//
	
With this call to down(), we were blocking the app With The Kernel Lock Held (!)
	// T0
	it->second->down();
	
	Genode::log( "    SemAcquire ", id, " .... cnt: ", old, " => ", it->second->cnt() );
	
	MTSAFE_GUARD_Epilogue;
	
#else

	if( debug >= 2 )
		Genode::log( __func__, " <", id, ">" );
	
	Semaphore * ge_sem = nullptr;
//	int old = 0;
	{
		MTSAFE_GUARD;
		auto it =
			semMap.find( id );
		
		if( semMap.end() == it )
			//
			return B_BAD_SEM_ID;
		
//		Genode::log( "    SemAcquire ", id, ", cnt=", it->second->cnt(), " => go to sleep/waiting for release_sem()" );
//		old = it->second->cnt();
		
		ge_sem = it->second;
		
		// !
		MTSAFE_GUARD_Epilogue;
	}
///FIXME-2: race condition: sem could have been deleted before we call down() on it... delete_sem() is called e.g. when closing a window/menu-window
	if( debug >= 2 )
		Genode::log( __func__, " <", id, "> -> sema.down()" );
	
	ge_sem->down();
	
//fix idea: implement "do not delete right away" pattern -- SemDelete should just "mark for deletion" ? like I do with Threads ? :
//+ check_if_was_deleted_during_race( ge_sem );  // will do: "for each mapSem entry, if mapSem->second == ge_sem && mapSem->markedForDeletion... WHAT ABOUT THE NORMAL CASE, how do we delete when NOT in a race situation ?  Well if it's *acquired*, we can use that "acquire" path to clean it up... And 3rd case, if deleted but never acquired, well...
//	Genode::log( "    SemAcquire ", id, " --> cnt: ", old, " => ", ge_sem->cnt() );
		// that can result in logging like this, dep. on concurrent threads (!) : "SemAcquire 10 --> cnt: 0 => 0"
#endif
	
	return B_OK;
}



status_t hog::HaiCalls_Srv::SemRelease( sem_id id )
{
	MTSAFE_GUARD;
	
	SemMap::iterator it =
		semMap.find( id );
	
	if( semMap.end() == it )
		//
		return B_BAD_SEM_ID;
	
//	Genode::log( "    SemRelease ", id, " with cnt=", it->second->cnt() );
//	const auto old = it->second->cnt();
	
	// T0
	it->second->up();
	
//	Genode::log( "    SemRelease ", id, ": cnt ", old, " => ", it->second->cnt() );
	
	MTSAFE_GUARD_Epilogue;
	
	return B_OK;
}

status_t hog::HaiCalls_Srv::SemGet( sem_id id, int32 * thread_count )
{
	MTSAFE_GUARD;
	
	auto it =
		semMap.find( id );
	
	if( semMap.end() == it )
		//
		return B_BAD_SEM_ID;
	
	*thread_count = it->second->cnt();
	
	MTSAFE_GUARD_Epilogue;
	
	return B_OK;
}



//#pragma mark - port -


port_id hog::HaiCalls_Srv::PortCreate( const char * /*name*/, int port_numbering_base )
{
	MTSAFE_GUARD;
	
#if 0
	port_id max = -1;
	{
		for( auto fifo = portQueues.begin(); fifo != portQueues.end(); fifo ++ )
		{
			if( fifo->first > max )
				max = fifo->first;
		}
	}
	
	// "max+1" is 0 in the worst case (-1+1==0)
	portQueues[max+1] = new PortFifo;
#else
	static const int Por1000 = 1000;//refactor...
	
	/**/
	static int next = 0;///ToDo: move this "last allocated" state var to class members
	const int port = port_numbering_base*Por1000 + next++;
	portQueues[ port ] = new PortFifo;
	
	if( next >= Por1000 )
		// also see: Broker's Hub::PTeamForPort() ...
		Genode::error( "Calling in Billy Boy! ", Por1000, " ports are NOT enough for everybody !" );
#endif  // ~0
	
	MTSAFE_GUARD_Epilogue;
	
	return port;
}


void hog::HaiCalls_Srv::PortClose( port_id port )
{
	MTSAFE_GUARD;
	
	PortFifo * fifo = lookupPort( port );
	
	if( fifo )
		fifo->close_down();
	else
		Genode::error( "PortClose error: no such port: ", port );
	
	MTSAFE_GUARD_Epilogue;
}

void hog::HaiCalls_Srv::PortDelete( port_id port )
{
	MTSAFE_GUARD;
	
	auto fifo = portQueues.find( port );
	
	if( portQueues.end() == fifo )
	{
		Genode::error( "PortDelete error: no such port: ", port );
		
		//
		return;
	}
	
	// T0
	delete fifo->second;
	portQueues.erase( fifo );
	
	MTSAFE_GUARD_Epilogue;
}


hog::HaiCalls_Srv::PortFifo *
 hog::HaiCalls_Srv::lookupPort( port_id port )
{
	//nope, we're called by others, and genode locks are /not/ recursive! -- _d_ MTSAFE_GUARD;
	
	auto fifo = portQueues.find( port );
	if( fifo != portQueues.end() )
		//
		return fifo->second;
		//
	
	return NULL;
}


void hog::HaiCalls_Srv::WaitBlockadeForPort( port_id port )
{
	// this is executed by the client thread, with no lock held of course
	
	//Semaphore copy;
	Semaphore * blockade = NULL;
	
	// local-scope the lock guard ! :
	// (we want ONLY the caller thread to block, the "kernel" should remain accessible to other threads):
	{
		MTSAFE_GUARD;
		
		PortFifo * fifo = lookupPort( port );
		
//		if( NULL == fifo )
//			return;// B_BAD_PORT_ID;
			//error()..
		
		if( fifo )
			blockade = fifo->blockade();
			//copy = fifo->semBlockade;
		else
			Genode::error( "waitforblockade: no such port: ", port );
		
		MTSAFE_GUARD_Epilogue;
	}
	
	///xxx race ? the Semaphore might get destroyed before we call down() ?
	// maybe we can make the "copy"-variable-based code work instead ?
	
	//Genode::log( "sem down, sleeping beauty awaits prince charming :-)" );
	//Genode::log( "read on port ", port, ": sem down, t- is ", blockade->cnt() );
//	Genode::log( "      t- WaitBlockadeForPort(", port, ")" );
	
	if( blockade )
		//copy.down();//
		blockade->down();
	else
		Genode::error( "(waitb) port's blockade is NULL ??" );
	
//	Genode::log( "      t+ WaitBlockadeForPort(", port, ")" );
	//Genode::log( "read on port ", port, ": sem down, t+ is ", blockade->cnt() );
}

void hog::HaiCalls_Srv::ReleaseBlockadeForPort( port_id port )
{
	//Semaphore copy;
	Semaphore * blockade = NULL;
	
	// local-scope the lock guard ! :
	{
		MTSAFE_GUARD;
		
		PortFifo * fifo = lookupPort( port );
		
		if( fifo )
			blockade = fifo->blockade();
			//copy = fifo->semBlockade;
		
		MTSAFE_GUARD_Epilogue;//
		
		if( NULL == fifo )
		{
			if( debug )
				// only with debug enabled (very noisy otherwise!) :
				Genode::error( "blockade not found (no such port) !? -- ReleaseBlockadeForPort: ", port );
			
			//
			return;
			//
		}
	}
	
	//	Genode::log( port, ": sem up, waking up sleeping beauty -- t- is ", blockade->cnt() );
	if( blockade )
	{
		blockade->up();//copy.up();
		//Genode::log( "write-on-port ", port, ": sem up, waking up sleeping beauty -- t+ is ", blockade->cnt() );
	}
	else
		Genode::error( "(relb) port's blockade is NULL ??" );
}


status_t hog::HaiCalls_Srv::PortInfo( port_id port, port_info & info )
{
	// USED BY
	// - BMessenger.IsValid() needs us to get the "exists/does not exist" status right.
	
	MTSAFE_GUARD;
	
	PortFifo * fifo = lookupPort( port );
	
	if( NULL == fifo )
		return B_BAD_PORT_ID;
	
	info.capacity = 999;  // for BLooper.Archive()...
//+	info.team = ?;
	
	MTSAFE_GUARD_Epilogue;
	
	return B_OK;
}


int32 hog::HaiCalls_Srv::PortCount( port_id port )
{
	MTSAFE_GUARD;
	
	PortFifo * fifo = lookupPort( port );
	
	if( NULL == fifo )
		return B_BAD_PORT_ID;
	
	///++ size = fifo->size(), then return size
	
	MTSAFE_GUARD_Epilogue;
	
	return fifo->size();
}


ssize_t hog::HaiCalls_Srv::PortPeek(
			port_id port,
			uint32,// flags,
			bigtime_t// timeout
			)
{
	MTSAFE_GUARD;
	
	PortFifo * fifo = lookupPort( port );
	
	//Genode::error( " (PortPeek(): there are ", portQueues.size(), " entries in map)");
	
	if( NULL == fifo )
		return B_BAD_PORT_ID;
	
///later: this "-1" seems to be of importance, in early testing.. ToDo: do more testing, with value set back to 0, looking at the impact on Menu.Track() et al, for comparison, then document the results
	if( fifo->size() <= 0 )
		return -1;//0;
		//+epilogue ?
	
	//Genode::error( "PortPeek of port ", port, " (", fifo, "): head item's size is ", fifo->size());
	
	const ssize_t size =
		fifo->front().size;
	
	MTSAFE_GUARD_Epilogue;
	
	return size;
}


status_t hog::HaiCalls_Srv::PortAppend(
			port_id port,
			int32 code,
			const void * buffer,
			size_t size
			)
{
	MTSAFE_GUARD;
	
	PortFifo * fifo = lookupPort( port );
	
	if( NULL == fifo )
		//
		return B_BAD_PORT_ID;
	
	if( fifo->is_closed() )
		//
		return B_BAD_PORT_ID;///?
	
	struct PortItem item {};
	{
		item.code = code;
		item.size = size;
		item.data = malloc( size );///+ if NULL.. return B_ERR..
			// <size> is equal to 0 sometimes, but that seems harmless (libc-malloc apparently
			// dodges calling the underlying "heap" with 0, which would trip an assert)
		
		Genode::memcpy(
			item.data,
			buffer,
			size
			);
	}
	
	fifo->push_back( item );
	
	//Genode::error( " write:(there are ", portQueues.size(), " different ports)");
	
	MTSAFE_GUARD_Epilogue;
	
	return B_OK;
}

ssize_t hog::HaiCalls_Srv::PortRead(
			port_id port,
			int32 * code,
			void * buffer,
			size_t size
			)
{
	MTSAFE_GUARD;
	
	// Is there a port/fifo for that port ID ?
	PortFifo * fifo = lookupPort( port );
	
	if( NULL == fifo )
		return B_BAD_PORT_ID;
	
	// should we return -1 or 0 here ? activate port tracing, seems -1 is more "quiet", whereas 0 triggers lots of unnecessary busy-looping ?
	///later: to be confirmed, but it seems the below is okay: '0' indicates a valid incoming message (with a code but without a buf/payload), and so if we lie about a msg being available (when there's none), this means ::read_port_etc() is *not* going to wait on a blockade, but will return immediately, which (for lack of actual msg) might result in a busy loop !
	
	// Is there a message/item waiting for us in that fifo ?
	if( fifo->size() <= 0 )
		return -1;//		return 0;//-1;  // ! (do not return 0 here, otherwise it causes trouble in BMenu.Track())
	
	struct PortItem & item = fifo->front();
	
	// Does the item's payload fit in the recipient buffer ?
	if( item.size > size )
	{
		Genode::error( "HaiCalls_Srv::PortRead: bail out to dodge *buffer overrun*, insufficient target buffer ", size, " bytes, vs. needed ", item.size );
		//
		return -1;
		//
	}
	
	// Copy/fill-out <code> and <buffer>:
	*code = item.code;
	Genode::memcpy(
		buffer,
		item.data,
		item.size
		);
	
	// Consume/erase original:
	free( item.data ); item.data = NULL;
	fifo->erase_first_item();
	
	MTSAFE_GUARD_Epilogue;
	
	return
		item.size;
}



//#pragma mark -th (support)-


hog::ThreadWithFunc::ThreadWithFunc( thread_func func, const char * name, void * data )
:	addyFunc( func )
	,funcData( data )
	,pthreadID( 0 )
	,haiThreadName( name )
	,lifeState( READY_TO_RUN )
	,deathNoticeSem( -1 )
{
	///later: now that we use pthreads instead of Genode::Thread directly, the latter gets a meaningless name (e.g. "pthread.0") inconsistent with this->name()... find a way to access the Genode::Thread() connected to a pthread, and setname() it, to ease debugging ?
	
	//Genode::log("-----THREAD+ ", addyFunc, " aka ", name);
	
	deathNoticeSem = create_sem( 0, name );
}

hog::ThreadWithFunc::~ThreadWithFunc()
{
	if( debug_thr )
		Genode::log( "  Dtor for thread: <", name(), "> <", pthreadID, ">  state: ", int(lifeState), " -- called from thread: ", pthread_self() );
	
	/*
	https://man7.org/linux/man-pages/man3/pthread_detach.3.html
		Either pthread_join(3) or pthread_detach() should be called for
		each thread that an application creates, so that system resources
		for the thread can be released.
	*/
	// join() must be called at least once, to free up the POSIX thread resources
	// (unless we're in detached state, but we don't use that here)
	// in case no-one else called it yet, call it here:
	
	switch( lifeState )
	{
		case READY_TO_RUN:
			Genode::warning( "thread Dtor called despite READY_TO_RUN state=", int(lifeState) );
		break;
		
		case RUNNING:
			// we are being deleted even though no-one called wait_for_thread() to reclaim its resources,
			// and we are unable to join() as the thread is still (?) running, so cancel the thread completely:
			//+assert( pthreadID != 0 )..
			Genode::error( "thread Dtor called despite RUNNING state ?!, pthreadID=", pthreadID );
			CancelAndMark();
		break;
		
		case NEEDS_JOINING:
		{
			// join/reclaim the thread resources first, and complete deletion only then:
//			raw_join();
			if( 0 == pthreadID )
				Genode::error( "thread.join <", haiThreadName, ">: threadID unexpectedly NULL ?!; return IP=", __builtin_return_address(0) );
			
			if( debug_thr )  Genode::log( "    pthread_join(", pthreadID, ") ..." );
			
			void * retval = nullptr;
			int ret = 0;
			Libc::with_libc([&] {
				ret = pthread_join( pthreadID, &retval );
			}); /* with_libc */
			
			if( debug_thr )  Genode::log( "    pthread_join(", pthreadID, ") done !" );
			
			if( ret )
				Genode::warning( "pthread_join unexpectedly returned with ", ret, " (errno=", errno, ") ****" );
		}
		break;
		
		case NEEDS_DELETING:
			// we are being deleted just as we were already-joined and in needs of deletion, all is good.
		break;
	}
	
	// delete_sem( deathNoticeSem ):
	// cannot do it here,
	// as this dtor is called with the Lock/Guard on, let our deleter call delete_sem with the Lock "off" !
}


void * hog::ThreadWithFunc::entry( void * hog_thread_uncast )
{
	ThreadWithFunc * hog_thread = static_cast<ThreadWithFunc*>( hog_thread_uncast );
	
	if( debug_thr >= 6 )
		Genode::log( "  > thread START (", hog_thread->name(), ")" );
	
	// T0 : execute C-address function
	hog_thread->addyFunc( hog_thread->funcData );
	
	// T+
	release_sem( hog_thread->deathNoticeSem );
	;
	// mark thread for deletion -- a thread is not allowed to (hackishly) delete itself ("Error: thread 'w>PopUpMenu' tried to self de-struct - sleeping forever.")
	// and it needs to stay around until someone calls wait_for_thread() on it anyway.
	if( hog_thread->lifeState != NEEDS_DELETING )
		hog_thread->lifeState = NEEDS_JOINING;
	
	if( debug_thr >= 6 )
		Genode::log( "  > thread END (", hog_thread->name(), "), semaphore released" );//, marked for deletion" );
	
	return nullptr;
	///later: retrieve the (int) return code from the Haiku-style entry, and return it casted as void*...?
}


void hog::ThreadWithFunc::CancelAndMark()
{
	// USED BY:
	// - Thread dtor
	// - exit_thread() (but that one should use pthread_exit() instead!)
	
//	if( debug_thr )
		Genode::warning( "    CancelAndMark of ", pthreadID, " <", name(), ">..." );
	
	// T-
	// mark for deletion/cleanup:
	lifeState = NEEDS_DELETING;/// or NEEDS_JOINING; ?
	
	bool needs_join = false;
	
	// dodge a crash..
	if( pthreadID != 0 )
	{
		// 'kill' thread:
		Libc::with_libc([&] {
			needs_join = (pthread_cancel(pthreadID) == 0);
		}); /* with_libc */
	}
	else
		Genode::error( "thr.CancelAndMark() called on nil thread ?!" );
	
///FIXME-2: had a Deskbar freeze/infinite-loop coming on the heels of some "CancelAndMark" tracing, closing its main menu ; related to this line? :
///ToDo: thread-cancel: the above pthread_cancel() might leak, seems we need to examine the return code and join() depending on status:
	if( needs_join )
		Genode::error( "leaked thread: cancelled the thread but it signals that it still requires an additional 'join'..." );
	/*
	Anything relevant in this snippet? :
	
		bool const needs_join = (pthread_cancel(native_thread().meta_data->pt) == 0);
	
		if (needs_join) {
			int const ret = pthread_join(native_thread().meta_data->pt, 0);
			if (ret)
				warning("pthread_join unexpectedly returned "
				        "with ", ret, " (errno=", errno, ")");
		}
		// inform core about the killed thread
		_cpu_session->kill_thread(_thread_cap);
	*/
	
	// T+ //? should we prevent multiple pthread_cancel() calls on the same thread ?
//	pthreadID = 0; //?
}


///	extern Genode::Env & HogEnv();
status_t hog::ThreadWithFunc::Resume()
{
	//Genode::log("       < ", HogEnv().pd().used_caps().value, " >");
	//int debug_thr = 9;
	
	if( lifeState != READY_TO_RUN )
	{
		if( debug_thr )
			Genode::log( "  ...thread-resume<", name(), ">: bailing out (pthread already launched)" );
		
		///later-review thread.Resume(): review this status (compare with BeBook and Haiku source code)
		
		//
		return B_ALREADY_RUNNING; //deviation from BeBook? does that bubble up to caller?
		//
	}
	// else: lifeState == READY_TO_RUN
	
	if( debug_thr )
		Genode::log( "  Thread.Resume() <", name(), "> state: ", int(lifeState) );
	
	// T- (otherwise we might race, see Pulse popup menu, Tracker's desktop popup, etc)
	lifeState = RUNNING;
	
	// T0 for <pthreadID>
	//
	int error = 0;
	Libc::with_libc([&] {
///FIXME-2: odd leak: we leak one capability (and some RAM !) (ie the whole pthread?), but mainly in "jam t7" (not at t6 or t8 level), and when using e.g. the deskbar... Due to the latter, especially, I need to file a ticket.
/// details: in jam t7 (only?), the created thread seems to be scheduled immediately, interrupting the "flow", whereas in in t6 and t8, the secong log() occurs immediately on the heels of this log():
/*
		Genode::log("       caps < ", HogEnv().pd().used_caps().value, " > ram <", HogEnv().pd().used_ram().value, ">");
*/
		error = pthread_create( &pthreadID, 0, entry, this );
		//Genode::log("       < ", HogEnv().pd().used_caps().value, " > err=", error);
	}); /* with_libc */
	
	if( error )
		Genode::error( "hikernel: pthread_create() failed with code: ", error );
		///+ return B_ERROR ?
	
	if( debug_thr )
		Genode::log( "   created pthread: ", pthreadID, " for <", name(), ">" );
	
	return B_OK;
}


pthread_t hog::ThreadWithFunc::PosixID() const
{
	return pthreadID;
}

const char * hog::ThreadWithFunc::name() const
{
	return haiThreadName.string();
}

sem_id hog::ThreadWithFunc::EndOfLife_Sem() const
{
	return deathNoticeSem;
}

void hog::ThreadWithFunc::Dump() const
{
	Genode::log( "      thread ", pthreadID, " state ", int(lifeState), " sem #", deathNoticeSem, " <", name(), ">" );
}



//#pragma mark -th-


thread_id  hog::HaiCalls_Srv::ThreadByName( const char * name )
{
	MTSAFE_GUARD;
	
	if( debug_thr >= 8 )
		Genode::log( " find_thread(", name, "):" );
	
	if( NULL == name )
	{
		pthread_t self = 0;
		Libc::with_libc([&] {
			self = pthread_self();  //Thread * wanted = Thread::myself();
		}); /* with_libc */
		
		if( debug_thr >= 8 )
			Genode::log( "  ..pthread_self -> ", self, " -- will lookup among ", threadsMap.size(), " candidates" );
		
		for( auto it = threadsMap.begin(); it != threadsMap.end(); it++ )
		{
			//Genode::log( " ..find_thread(null) is looking at #", it->first, " <", it->second->PosixID(), "> <", it->second->name(), ">" );
			
			if( it->second->PosixID() == self )
			{
				if( debug_thr >= 9 )
					Genode::log( "   find_thread(null): success: => ", it->first );
				
				//
				return it->first;
				//
			}
		}
		
		// we should never get past that for(), i.e. never fail to find 'self': all Haiku
		// threads (including main()) run in a pthread, and Genode threads should not
		// run Haiku code, i.e. should not land here...
///ToDo-2: almost there! We're still getting ONE instance of this, in Pulse, after exit_thread()/CancelAndMark(), presumably as it removes the thread from thread registry too soon, leading to find_thread(NULL) no longer finding itself
///ToDo: now that we have e.g. NetworkKit (in "jam t9" layer) we have lots of "static BLocker"s to deal with... Those "static" are necessarily
// executed before any Haiku thread is spawned and before main() is called, thus find_thread() returns NULL, which perturbs BLocker...
// How badly is it affected ? How to get locking back on track ?
// e.g. registrar (or anyone linked against t9) has instances of IsLocked/Acquire: BUrlContext ctor -> SynchronizedHashMap.Put() -> Acquire/IsLocked() on BLocker("synchronized hash map")
		Genode::warning( "find_thread(null): B_NAME_NOT_FOUND, pthread_self says: ", pthread_self(), " caller IP: ", __builtin_return_address(0) );
/// Maybe move BUrlContext initialization to a separate method, to be called in HoG_init() or even right as main() gets started, at thread creation ?  Or  it could be find_thread()/ThreadByName() itself which keeps track of whether we're in "pre-main" or "in-main()" context, and returns a special thread_id
	}
	else
	{
		for( auto it = threadsMap.begin(); it != threadsMap.end(); it++ )
		{
			if( debug_thr >= 9 )
				Genode::log( " ..thread-by-name<", name, "> is looking at #", it->first, " <", it->second->name(), ">" );
			
			if( it->second->name() && 0 == Genode::strcmp(name, it->second->name()) )
				//
				return it->first;
				//
		}
	}
	
	if( debug_thr >= 3 )
		Genode::warning( "find_thread <", name, ">: B_NAME_NOT_FOUND" );
	
	MTSAFE_GUARD_Epilogue;
	
	return B_NAME_NOT_FOUND;
}


thread_id
	hog::HaiCalls_Srv::ThreadCreate(
			thread_func func,
			const char * name,
			void * data
			)
{
	// T- -- UNlocked, since this calls SemCreate()
	ThreadWithFunc * th = new ThreadWithFunc( func, name, data );
	
	// T0
	thread_id id = -1;
	{
		MTSAFE_GUARD;
		
		id = nextThread++;  // !
		threadsMap[id] = th;
	}
	
	return id;
}



// IMPLEM. NOTE: on multiple concurrent join'ing for the same thread:
// In a few cases (e.g. BAlert : window thread, "menu_tracking" thread, etc?) we should be able to
// serve 2+ concurrent wait_for_thread() calls on the same thread.   The underlying pthread_join()
// does not seem to support that, so we implement the waiting thanks to a "death" semaphore instead.
//
status_t  hog::HaiCalls_Srv::ThreadJoin( thread_id thread )
{
	if( debug_thr >= 6 )
		Genode::log( "  ThreadJoin( ", thread, " )" );
	
	sem_id death_sem = -1;
	{
		// get the lock for a short time and release it *before* blocking on join() :
		MTSAFE_GUARD;
		
		ThreadWithFunc * th = lookupThread( thread );
		
		if( NULL == th )
			//
			return B_BAD_THREAD_ID;
			//
		
		if( debug_thr >= 5 )
			Genode::log( "  ...join-waitforthread (", thread, ") <", th->name(), ">: make sure the thread is resume()d..." );
		
		// BeBook: wait_for_thread *implies* resume_thread() :
		// T-
		status_t oops = //ret_start =
			th->Resume();
		if( oops != B_OK && oops != B_ALREADY_RUNNING )
			Genode::error( "oops, we're going to call join() even though the thread did NOT start..." );
		
		// using <th> after unlocking would be unsafe, but semaphore are (or will we soon...) 'life-cycle safe' :
		death_sem = th->EndOfLife_Sem();
		
		///ToDo: race: make sure death-sem's life-cycle is indeed 'safe' (what if it disappears between this un-lock and the next lock+join())
	}
	
	//
	// T0 -- as noted up there, we do not call pthread_join(), we use a "death semaphore":
	//
	auto res = SemAcquire( death_sem );
	
	// Death semaphore acquired! Now, free (our side's) resources:
	// - delete <ThreadWithFunc>
	// - delete_sem()
	//
	// That ThreadWithFunc deletion/destructor WILL end up calling pthread_join(), only
	// for the *first* thread out of the SemAcquire() gate. Subsequent wait_for_thread()s
	// will have no additional work to do beyond waiting for the death semaphore.
	//
	sem_id delete_this_sem = -1;
	{
		MTSAFE_GUARD;
		
///ToDo: an even better sequence:  1) lock  2) extract the Thread from the map<>  3) unlock (!)  4) delete the Thread... This way, I can move the delete_sem() call to the Thread Dtor where it belongs !
		ThreadWithFunc * th = lookupThread( thread );
		
		if( NULL == th )
			// the second, third, etc concurrent wait_for_thread()s will get this:
			Genode::warning( "join: thread (", thread, ") has vanished, someone else took care of collecting it?" );
		else
		{
			auto it = threadsMap.find( thread );
			delete_this_sem = th->EndOfLife_Sem();
			
			threadsMap.erase( it );
			delete th; th = nullptr;
		}
	}
	// now that the lock/guard is off, we can do this:
	if( delete_this_sem >= 0 )
		delete_sem( delete_this_sem );
	
	return res;
}


hog::ThreadWithFunc *
	hog::HaiCalls_Srv::lookupThread( thread_id thread )
{
	// Usage: before calling this, an MTSAFE_GUARD *must* be holding the global lock (caller's responsibility, not ours) during execution.
	
	auto it =
		threadsMap.find( thread );
	
	if( threadsMap.end() == it )
		return NULL;
	
	return it->second;
}

///FIXME-2: get rid of this public accessor? Currently used by ::exit_thread()... ** Create a ThreadExit() method instead! **  Note: exit_thread() is primarily used in BLooper::Quit() (which is called in one of the BAlert modes)
hog::ThreadWithFunc *
	hog::HaiCalls_Srv::LookupThread( thread_id thread )
{
	MTSAFE_GUARD;
	
	return lookupThread( thread );
}

status_t hog::HaiCalls_Srv::ThreadStart( thread_id thread )
{
	if( debug_thr )
		Genode::log( " thread.Start( ", thread, " )" );
	
	MTSAFE_GUARD;
	
	ThreadWithFunc * th =
		lookupThread( thread );
	
	if( NULL == th )
		//
		return B_BAD_THREAD_ID;
		//
	
	//Genode::log( "thread Start! id:", thread, " name:", th->name());
	
	// T0
	return
		th->Resume();
}


