libraries/clogger/src/SensibleServer.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Wed, 23 Jun 2010 15:52:26 +0100
changeset 0 7f656887cf89
permissions -rw-r--r--
First submission to Symbian Foundation staging server.

// 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);
		}
	}