libraries/clogger/src/SensibleServer.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Thu, 26 Aug 2010 00:49:35 +0100
changeset 37 534b01198c2d
parent 0 7f656887cf89
permissions -rw-r--r--
Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase. Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.

// SensibleServer.cpp
// 
// Copyright (c) 2006 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
// 
// Initial Contributors:
// Accenture - Initial contribution
//

#include "SensibleServer.h"
#include "SensibleServer_server_specific.h"

#define DebugPanic() User::Panic(KDebugPanic, __LINE__)
#define ClientPanic(aMsg) PanicClient(aMsg, - __LINE__)

inline CShutdown::CShutdown()
	:CTimer(-1)
	{CActiveScheduler::Add(this);}
inline void CShutdown::ConstructL()
	{CTimer::ConstructL();}
inline void CShutdown::Start(TInt aDelay)
	{
	if (aDelay)
		{
		After(aDelay);
		}
	}

CSensibleServer::CSensibleServer()
	:CServerBase(0,ESharableSessions)
	{}

CSensibleSession::CSensibleSession()
	: iCallbackQ(_FOFF(CCallbackContext, iLink))
	{}

inline CSensibleServer& CSensibleSession::Server()
	{return *static_cast<CSensibleServer*>(const_cast<CServerBase*>(CSessionBase::Server()));}

inline CFilteringScheduler* CSensibleServer::Scheduler()
	{return iScheduler;}

#ifdef RUN_SERVER_WITH_EIKONENV

#include <eikenv.h>
#include <eikappui.h>

class CSensibleAppUi : public CEikAppUi
	{
public:
	void ConstructL();
	~CSensibleAppUi();

private:
	CSensibleServer* iServer;
	};

void CSensibleAppUi::ConstructL()
	{
	BaseConstructL(ENoAppResourceFile /*|ENoScreenFurniture*/);

	//
	// create the server
	iServer = new(ELeave) CServer_Class_Name();
	iServer->ConstructL();
	}

CSensibleAppUi::~CSensibleAppUi()
	{
	delete iServer;
	}

// Have to derive from CONE scheduler otherwise ccoeenv complains mightily
#define SCHEDULER_SUPER CCoeScheduler
#define SCHEDULER_CONSTRUCTOR CCoeScheduler(CCoeEnv::Static())
inline void ExitScheduler() { CBaActiveScheduler::Exit(); }

#else

#define SCHEDULER_SUPER CActiveScheduler
#define SCHEDULER_CONSTRUCTOR CActiveScheduler()
inline void ExitScheduler() { CActiveScheduler::Stop(); }

#endif

class CFilteringScheduler : public SCHEDULER_SUPER
	{
public:
	CFilteringScheduler();
	void WaitForAnyRequest();
	//void OnlyRunThisObject(CActive* aActive); // This doesn't work atm!
	void RunEverythingExcept(CActive* aActive, CActive* aActive2);
	void StopFiltering();
	
private:
	struct SObj {
		CActive* iObject;
		TInt iCachedStatus;
		};
	static const TInt KNumFilteredObjects = 2;
	SObj iFilteredObjects[KNumFilteredObjects];
	};

inline TServerStart::TServerStart()
	{}

void TServerStart::SignalL()
//
// Signal the owning thread that the server has started successfully
// This may itself fail
//
	{
#ifdef EKA2
	RProcess::Rendezvous(KErrNone);
#else
	RThread starter;
	User::LeaveIfError(starter.Open(iId));
	starter.RequestComplete(iStatus,KErrNone);
	starter.Close();
#endif
	}


///////////////////////

#ifndef __HIDE_IPC_V1__
void CSensibleSession::CreateL(const CServer& aServer)
//
// 2nd phase construct for sessions - called by the CServer framework
//
	{
	CSharableSession::CreateL(aServer);	// does not leave
	CreateL();
	}
#endif

void CSensibleSession::CreateL()
	{
	Server().AddSession();
	}

CSensibleSession::~CSensibleSession()
	{
	Server().DropSession();
	}

static void JustPanic(TAny*)
	{
	User::Panic(_L("Crash!"), 0);
	}

extern void CleanupPanicPushL()
	{
	CleanupStack::PushL(TCleanupItem(&JustPanic, 0));
	}

void CSensibleSession::ServiceL(const RMessage& aMessage)
//
// Handle a client request.
// Leaving is handled by CSensibleServer::RunError() which reports the error code
// to the client
//
	{
	switch (aMessage.Function())
		{
	case ERegisterCallbackNotifier:
		__ASSERT_ALWAYS(!iCallbackPending, ClientPanic(aMessage)); // Can't call Register is there's something already registered
		iCallbackNotifier = aMessage;
		iCallbackPending = ETrue;
		CompleteNextCallback();
		break;
	case EGetCallbackContext:
		{
		__ASSERT_ALWAYS(!iCallbackQ.IsEmpty(), ClientPanic(aMessage));
		CCallbackContext* c = iCallbackQ.First();
		__ASSERT_ALWAYS(c->Flag(EActive), ClientPanic(aMessage));
		__ASSERT_ALWAYS(c->CallbackHasContext(), ClientPanic(aMessage));
		aMessage.WriteL(SLOT(aMessage, 0), *c->Context());
		aMessage.Complete(KErrNone);
		if (!c->CallbackRequiresResult())
			{
			iCallbackQ.Remove(*c);
			delete c;
			}
		break;
		}
	case EWriteCallbackResultAndReregister:
		{
		__ASSERT_ALWAYS(!iCallbackPending, ClientPanic(aMessage)); // Can't call Register is there's something already registered
		__ASSERT_ALWAYS(!iCallbackQ.IsEmpty(), ClientPanic(aMessage));
		CCallbackContext* c = iCallbackQ.First();
		__ASSERT_ALWAYS(c->Flag(EActive), ClientPanic(aMessage));
		__ASSERT_ALWAYS(c->CallbackRequiresResult(), ClientPanic(aMessage));
		
		// Get the reregistering out of the way
		iCallbackNotifier = aMessage;
		iCallbackPending = ETrue;
		
		if (aMessage.Int2() < 0)
			{
			// Leave code
			c->SetFlags(EResultIsLeaveCode);
			c->Result().integer = aMessage.Int2();
			}
		else if (c->Flag(EResultHBufC16))
			{
			HBufC16* result = HBufC16::New(aMessage.Int2());
			if (!result) 
				{
				c->SetFlags(EResultIsLeaveCode);
				c->Result().integer = KErrNoMemory;
				}
			else
				{
				*c->Result().l = result;
				TPtr16 ptr(result->Des());
				aMessage.ReadL(SLOT(aMessage, 1), ptr);
				}
			}
		else if (c->Flag(EResultHBufC8))
			{
			HBufC8* result = HBufC8::New(aMessage.Int2());
			if (!result) 
				{
				c->SetFlags(EResultIsLeaveCode);
				c->Result().integer = KErrNoMemory;
				}
			else
				{
				*c->Result().s = result;
				TPtr8 ptr(result->Des());
				aMessage.ReadL(SLOT(aMessage, 1), ptr);
				}
			}
		else
			{
			// It's a TPkg
			aMessage.ReadL(SLOT(aMessage, 1), *c->Result().pkg);
			}
		CActiveScheduler::Stop();
		break;
		}
	case ECancelCallbackNotifier:
		{
		if (iCallbackPending)
			{
			iCallbackNotifier.Complete(KErrCancel);
			iCallbackPending = EFalse;
			}
		aMessage.Complete(KErrNone);
		break;
		}
	case EDummy:
		aMessage.Complete(KErrNone);
		break;

	default:
		if (!DoServiceL(aMessage))
			{
			PanicClient(aMessage, EPanicIllegalFunction);
			}
		break;
		}
	}

TBool CSensibleSession::DoServiceL(const RMessage& aMessage)
	{
	// Subclasses override this!
	aMessage.Complete(KErrNone);
	return ETrue;
	}

void CShutdown::RunL()
//
// Initiate server exit when the timer expires
//
	{
	ExitScheduler();
	}

void CSensibleServer::ConstructL()
//
// 2nd phase construction - ensure the timer and server objects are running
//
	{
	StartL(KMyServerName);
	iShutdown.ConstructL();
	// ensure that the server still exits even if the 1st client fails to connect
	iShutdown.Start(TransientServerShutdownTime());

	// Now set up our special scheduler. This is tricky because of good old eikonenv doing stuff differently
	// Basically without eikonenv, RunServer owns the old scheduler so we can't delete it
	// However eikonenv will delete the new one as part of its shutdown!

	iScheduler = new(ELeave) CFilteringScheduler();
	iOldScheduler = CActiveScheduler::Replace(iScheduler);
	
#ifdef RUN_SERVER_WITH_EIKONENV
	DISOWN(iOldScheduler);
#endif
	}

CSensibleServer::~CSensibleServer()
	{
#ifndef RUN_SERVER_WITH_EIKONENV
	DISOWN(iScheduler); // To mimic what CCoeEnv does
#endif
	}

#ifdef __HIDE_IPC_V1__
CSessionBase* CSensibleServer::NewSessionL(const TVersion& /*aVersion*/, const RMessage& /*aMessage*/) const
#else
CSessionBase* CSensibleServer::NewSessionL(const TVersion& /*aVersion*/) const
#endif
//
// Cretae a new client session. This should really check the version number.
//
	{
	return new(ELeave) CSensibleSession();
	}

void CSensibleServer::AddSession()
//
// A new session is being created
// Cancel the shutdown timer if it was running
//
	{
	++iSessionCount;
	iShutdown.Cancel();
	}

void CSensibleServer::DropSession()
//
// A session is being destroyed
// Start the shutdown timer if it is the last session.
//
	{
	if (--iSessionCount==0 && CActiveScheduler::Current()) // Check we have a scheduler, because if the server is being shut down there won't be one (and there'll be no point starting a shutdown timer
		iShutdown.Start(TransientServerShutdownTime());
	}


TInt CSensibleServer::RunError(TInt aError)
//
// Handle an error from CSensibleSession::ServiceL()
// A bad descriptor error implies a badly programmed client, so panic it;
// otherwise report the error to the client
//
	{
	if (aError==KErrBadDescriptor)
		PanicClient(Message(),EPanicBadDescriptor);
	else
		Message().Complete(aError);
	//
	// The leave will result in an early return from CServer::RunL(), skipping
	// the call to request another message. So do that now in order to keep the
	// server running.
	ReStart();
	return KErrNone;	// handled the error fully
	}

/*void CSensibleServer::BlockAllAOsExceptServerRequests()
	{
	Scheduler()->OnlyRunThisObject(this);
	}*/

void CSensibleServer::BlockRequestsFrom(CActive* aActive1, CActive* aActive2)
	{
	Scheduler()->RunEverythingExcept(aActive1, aActive2);
	}

void CSensibleServer::StopBlocking()
	{
	Scheduler()->StopFiltering();
	}

TInt CSensibleServer::TransientServerShutdownTime() const
	{
	return 2000000; // Default to 2 seconds
	}

void PanicClient(const RMessage& aMessage, TInt aPanic)
//
// RMessage::Panic() also completes the message. This is:
// (a) important for efficient cleanup within the kernel
// (b) a problem if the message is completed a second time
//
	{
	__DEBUGGER();
	aMessage.Panic(KDebugPanic, aPanic);
	}

static void RunServerL(TServerStart& aStart)
//
// Perform all server initialisation, in particular creation of the
// scheduler and server and then run the scheduler
//
	{

#ifndef RUN_SERVER_WITH_EIKONENV
	// create and install the active scheduler we need
	CActiveScheduler* s=new(ELeave) CActiveScheduler;
	CleanupStack::PushL(s);
	CActiveScheduler::Install(s);
#endif

	//
	// naming the server thread after the server helps to debug panics
#ifdef __SECURE_API__
	User::LeaveIfError(User::RenameThread(KMyServerName));
#else
	User::LeaveIfError(RThread().Rename(KMyServerName));
#endif

#ifdef RUN_SERVER_WITH_EIKONENV
	// In this case, the server creation/destruction is pushed into CSensibleAppUi

	// Give ourselves a eikonenv
	CEikonEnv* env = new CEikonEnv;
	CEikAppUi* appui = NULL;
	TInt err = KErrNone;
	if (env != NULL)
		{
		TRAP(err,
			env->ConstructL(EFalse);
			appui = new (ELeave)CSensibleAppUi();
			appui->ConstructL();
			env->SetAppUi(appui);
			);
		}
	if (err == KErrNone)
		{
		//
		// Initialisation complete, now signal the client
		aStart.SignalL();
		env->ExecuteD();
		}
	else
		{
		if (env != NULL)
			{
			env->DestroyEnvironment();
			}
		User::Leave(err); // This will tell the client that something's gone wrong
		}
#else	

	//
	// create the server
	CSensibleServer* server = new(ELeave) CServer_Class_Name();
	CleanupStack::PushL(server);
	server->ConstructL();
	//
	// Initialisation complete, now signal the client
	aStart.SignalL();
	//
	// Ready to run
	CActiveScheduler::Start();

	CleanupStack::PopAndDestroy(2, s); // server, scheduler

#endif
	}

static TInt RunServer(TServerStart& aStart)
//
// Main entry-point for the server thread
//
	{
	__UHEAP_MARK;
	//
	CTrapCleanup* cleanup=CTrapCleanup::New();
	TInt r=KErrNoMemory;
	if (cleanup)
		{
//#ifdef _DEBUG
//		TRAP(r, CleanupPanicPushL(); RunServerL(aStart); CleanupStack::Pop());
//#else
		TRAP(r,RunServerL(aStart));
//#endif
		delete cleanup;
		}
	//
	__UHEAP_MARKEND;
	return r;
	}


#ifndef EKA2

// The server binary is an "EPOCEXE" target type
// Thus the server parameter passing and startup code for WINS and EPOC are
// significantly different.

#ifdef __WINS__

// In WINS, the EPOCEXE target is a DLL with an entry point called WinsMain,
// taking no parameters and returning TInt. This is not really valid as a thread
// function which takes a TAny* parameter which we need.
//
// So the DLL entry-point WinsMain() is used to return a TInt representing the
// real thread function within the DLL. This is good as long as
// sizeof(TInt)>=sizeof(TThreadFunction).
//

static TInt ThreadFunction(TAny* aParms)
//
// WINS thread entry-point function.
// The TServerStart objects is passed as the thread parameter
//
	{
	return RunServer(*static_cast<TServerStart*>(aParms));
	}

IMPORT_C TInt WinsMain();
EXPORT_C TInt WinsMain()
//
// WINS DLL entry-point. Just return the real thread function 
// cast to TInt
//
	{
	return reinterpret_cast<TInt>(&ThreadFunction);
	}

TInt E32Dll(TDllReason)
	{
	return KErrNone;
	}

#else

//
// In EPOC, the EPOCEXE target is a process, and the server startup
// parameters are encoded in the command line
//

TInt TServerStart::GetCommand()
	{
	RProcess p;
	if (p.CommandLineLength()!=sizeof(TServerStart)/sizeof(TText))
		return KErrGeneral;
	TPtr ptr(reinterpret_cast<TText*>(this),0,sizeof(TServerStart)/sizeof(TText));
	p.CommandLine(ptr);
	return KErrNone;
	}

TInt E32Main()
//
// Server process entry-point
// Recover the startup parameters and run the server
//
	{
	TServerStart start;
	TInt r=start.GetCommand();
	if (r==KErrNone)
		r=RunServer(start);
	return r;
	}

#endif

#else

TInt E32Main()
//
// Server process entry-point
//
	{
	TServerStart start;
	TInt r = RunServer(start);
	return r;
	}

#endif

//// CCallbackContext ////

CCallbackContext::CCallbackContext(TCallbackCode aCode)
	: iCallback(aCode)
	{
	iCallback.iCode = aCode;
	}

CCallbackContext::~CCallbackContext()
	{
	__ASSERT_DEBUG(!Flag(EActive), DebugPanic());
	delete iContext;
	}

void CCallbackContext::SetFlags(TInt aFlags)
	{
	iFlags |= aFlags;
	}

TBool CCallbackContext::Flag(TInt aFlags) const
	{
	return iFlags & aFlags;
	}

void CCallbackContext::ClearFlags(TInt aFlags)
	{
	iFlags = iFlags & ~aFlags;
	}

TBool CCallbackContext::CallbackRequiresResult() const
	{
	//TODO
	return EFalse;
	}

TBool CCallbackContext::CallbackHasContext() const
	{
	return (iContext != NULL);
	}

void CCallbackContext::SetResult(TDes8& aPkg)
	{
	iResult.pkg = &aPkg;
	ClearFlags(EResultHBufC8 | EResultHBufC16);
	}

void CCallbackContext::SetResult(HBufC8*& aResult)
	{
	iResult.s = &aResult;
	SetFlags(EResultHBufC8);
	ClearFlags(EResultHBufC16);
	}

void CCallbackContext::SetResult(HBufC16*& aResult)
	{
	iResult.l = &aResult;
	SetFlags(EResultHBufC16);
	ClearFlags(EResultHBufC8);
	}

HBufC8* CCallbackContext::Context()
	{
	return iContext;
	}

CCallbackContext::TResult& CCallbackContext::Result()
	{
	return iResult;
	}

TServerCallback& CCallbackContext::Callback()
	{
	return iCallback;
	}

TCallbackWriter CCallbackContext::Writer()
	{
	TCallbackWriter res(iCallback, &iContext);
	return res;
	}

//// CFilteringScheduler ////

CFilteringScheduler::CFilteringScheduler()
: SCHEDULER_CONSTRUCTOR
	{}


/*
void CFilteringScheduler::OnlyRunThisObject(CActive* aActive)
	{
	__ASSERT_ALWAYS(!iObject, DebugPanic());
	iObject = &aActive->iStatus;
	iOnlyRunThisObject = ETrue;
	User::Panic(_L("OnlyRunThisObject doesn't work yet!"), 0);
	}
*/

void CFilteringScheduler::RunEverythingExcept(CActive* aActive, CActive* aActive2)
	{
	__ASSERT_ALWAYS(!iFilteredObjects[0].iObject, DebugPanic());
	iFilteredObjects[0].iObject = aActive;
	iFilteredObjects[1].iObject = aActive2;

	iFilteredObjects[0].iCachedStatus = KRequestPending;
	iFilteredObjects[1].iCachedStatus = KRequestPending;
	}

void CFilteringScheduler::StopFiltering()
	{
	for (TInt i = 0; i < KNumFilteredObjects; i++)
		{
		SObj& obj = iFilteredObjects[i];
		if (obj.iObject && obj.iCachedStatus != KRequestPending)
			{
			TRequestStatus* stat = &obj.iObject->iStatus;
			User::RequestComplete(stat, obj.iCachedStatus); // Since we consumed the signal from the previous complete, we need to re-signal by calling RequestComplete rather than just updating the object status ourselves
			}
		obj.iObject = NULL;
		}
	}

void CFilteringScheduler::WaitForAnyRequest()
	{

	if (!iFilteredObjects[0].iObject)
		{
		SCHEDULER_SUPER::WaitForAnyRequest();		
		return;
		}

	for (;;)
		{
		SCHEDULER_SUPER::WaitForAnyRequest();
		TBool found = EFalse;
		for (TInt i = 0; i < KNumFilteredObjects; i++)
			{
			SObj& obj = iFilteredObjects[i];
			TBool isReadyToRun = obj.iObject && obj.iObject->IsActive() && obj.iObject->iStatus != KRequestPending;
			if (isReadyToRun)
				{
				// Our target object has completed, so mark it back as pending, and consume the signal
				ASSERT(obj.iCachedStatus == KRequestPending); // If this is already set something has gone quite wrong with our logic
				obj.iCachedStatus = obj.iObject->iStatus.Int();
				*((TInt*)&obj.iObject->iStatus) = KRequestPending; // Cast this to a TInt* to prevent TRequestStatus::operator= changing the flags
				found = ETrue;
				break;
				}
			}
		if (!found) break; // It wasn't one of our objects that completed so no need to go round the loop again
		}
	}

//// TCallbackWriter ////

TCallbackWriter::TCallbackWriter(TServerCallback& aCallback, HBufC8** aContext)
: iCallback(aCallback), iContext(aContext), iBuf((TUint8*)aCallback.iData.Ptr(), aCallback.iData.MaxLength()), iInContext(EFalse)
	{
	if (iContext) *iContext = NULL;
	}

void TCallbackWriter::AddL(const TDesC8& aData, char* aType)
	{
	__ASSERT_DEBUG(aData.Length(), DebugPanic());
	__ASSERT_DEBUG(aType, DebugPanic());
	TInt bytesRemaining = iBuf.MaxSize() - iBuf.Size();

	TInt typeSize = 1;
	if (*aType == 'D')
		{
		// TDesC16s need to be 2-byte aligned, so make sure the data after the type byte will be
		if (!(iBuf.Length() & 1)) typeSize = 2;
		}

	if (aData.Size() + typeSize > bytesRemaining)
		{
		// No room for arg and type
 		if (!iContext) User::Leave(KErrNoMemory);
		if (!iInContext)
			{
			// so construct context
			if (*aType == 'D') typeSize = 2;
			*iContext = HBufC8::NewL(aData.Length() + typeSize);
			iInContext = ETrue;
			}
		else
			{
			// realloc
			HBufC8* newContext = (*iContext)->ReAlloc(Max(iBuf.MaxSize() * 2, iBuf.MaxSize() + aData.Size() + typeSize));
			if (!newContext)
				{
				delete *iContext;
				*iContext = NULL;
				User::Leave(KErrNoMemory);
				}
			*iContext = newContext;
			}
		iBuf.Set((*iContext)->Des());
		}
	iBuf.Append(*aType);
	if (typeSize == 2) iBuf.Append('-'); // Padding
	iBuf.Append(aData);
	if (iInContext) 
		{
		iCallback.iContextLength = iBuf.Length();
		}
	else
		{
		iCallback.iData.SetLength(iBuf.Length()); // Because a TPtr pointing to a buf doesn't behave like one pointing to an HBufC, sigh...
		}

	}

#define ADD(T, arg, type) { TPckg<T> x(arg); AddL(x, #type); }

void TCallbackWriter::AddL(TInt aInt)		{ ADD(TInt, aInt, i); }
void TCallbackWriter::AddL(TUint aInt)		{ ADD(TUint, aInt, u); }
void TCallbackWriter::AddL(TPoint aPoint)	{ ADD(TPoint, aPoint, P); }
void TCallbackWriter::AddL(TSize aSize)		{ ADD(TSize, aSize, S); }
void TCallbackWriter::AddL(TRgb aRgb)		{ ADD(TRgb, aRgb, G); }
void TCallbackWriter::AddL(TRect aRect)		{ ADD(TRect, aRect, R); }

void TCallbackWriter::AddL(const TDesC16& aDesc)
	{
	ADD(TInt, aDesc.Length(), i);
	TPtrC8 x((TUint8*)aDesc.Ptr(), aDesc.Size());
	AddL(x, "D");
	}

void TCallbackWriter::AddL(const TDesC8& aDesc)
	{
	ADD(TInt, aDesc.Length(), i);
	AddL(aDesc, "8");
	}

//// CSensibleSession ////

void CSensibleSession::QueueCallbackL(CCallbackContext* aContext)
	{
	iCallbackQ.AddLast(*aContext);
	if (aContext->CallbackRequiresResult())
		{
		if (aContext->Flag(EBlockServer))
			{
			//Server().Scheduler()->OnlyRunThisObject(&Server()); //TODO fix this at some point!
			}
		__ASSERT_ALWAYS(!iWaitingForCallbackResult, DebugPanic()); // This means someone queued a callback that required a result without specifying EBlockServer, and in the meantime someone else has queued another callback requiring a result. This isn't supported! If there is the remotest chance this could happen, then all must specify EBlockServer.
		iWaitingForCallbackResult = ETrue;
		CompleteNextCallback();
		CActiveScheduler::Start();
		// When we reach here, the client stuff has finished and aContext has our result in
		// We call CompleteNextCallback again here since to reach this point the server must have received a EWriteCallbackResultAndReregister, which means it's ready for another callback
		CompleteNextCallback();
		if (aContext->Flag(EBlockServer))
			{
			Server().Scheduler()->StopFiltering();
			}
		iWaitingForCallbackResult = EFalse;
		if (aContext->Flag(EResultIsLeaveCode))
			{
			iCallbackQ.Remove(*aContext);
			TInt err = aContext->Result().integer;
			delete aContext;
			User::Leave(err);
			}
		// Nothing else needed
		}
	else
		{
		CompleteNextCallback();
		}
	}

TBool CSensibleSession::DispatchCallback(TServerCallback& aCallback)
	{
	if (!iCallbackPending)
		{
		// Client not ready to be notified
		return EFalse;
		}

	TPckg<TServerCallback> pkg(aCallback);
	TRAPD(err, iCallbackNotifier.WriteL(SLOT(iCallbackNotifier, 0), pkg));
	if (err)
		{
		PanicClient(iCallbackNotifier, EPanicBadDescriptor);
		iCallbackPending = EFalse;
		return EFalse;
		}
	
	iCallbackNotifier.Complete(KErrNone);
	iCallbackPending = EFalse;
	return ETrue;
	}

void CSensibleSession::CompleteNextCallback()
	{
	if (!iCallbackPending)
		{
		// Client not ready to be notified
		return;
		}
	else if (iCallbackQ.IsEmpty())
		{
		// Nothing to complete yet
		return;
		}
	
	CCallbackContext* c = iCallbackQ.First();
	TPckg<TServerCallback> pkg(c->Callback());
	#ifdef __HIDE_IPC_V1__
	TRAPD(err, iCallbackNotifier.WriteL(0, pkg));
	#else
	TRAPD(err, iCallbackNotifier.WriteL(iCallbackNotifier.Ptr0(), pkg));
	#endif
	if (err)
		{
		PanicClient(iCallbackNotifier, EPanicBadDescriptor);
		iCallbackPending = EFalse;
		return;
		}
	
	iCallbackNotifier.Complete(KErrNone);
	iCallbackPending = EFalse;
	
	if (!c->CallbackRequiresResult() && !c->CallbackHasContext())
		{
		iCallbackQ.Remove(*c);
		delete c;
		}
	else
		{
		c->SetFlags(EActive);
		}
	}