userlibandfileserver/fileserver/sfile/sf_thread.cpp
changeset 0 a41df078684a
child 2 4122176ea935
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/userlibandfileserver/fileserver/sfile/sf_thread.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,1369 @@
+// Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the License "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:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+// f32\sfile\sf_thread.cpp
+// 
+//
+
+#include "sf_std.h"
+#include <u32exec.h>
+#include "sf_file_cache.h"
+
+#define __CHECK_DRVNUM(d) {__ASSERT_DEBUG(d>=EDriveA && d<=EDriveZ,Fault(EFsThreadBadDrvNum));}
+
+#ifdef __X86__
+const TInt KRequestThreadStackSize = 0x4000;
+#else
+const TInt KRequestThreadStackSize = 0x3000;
+#endif
+
+const TInt KFinaliseTimerPeriod = 10 * 1000 * 1000;	// default 10S finalisation timeout
+
+TFsDriveThread FsThreadManager::iFsThreads[KMaxDrives];
+TUint FsThreadManager::iMainId=0;
+CDisconnectThread* FsThreadManager::iDisconnectThread=NULL;
+TUint FsThreadManager::iDisconnectThreadId=0;
+
+TFsDriveThread::TFsDriveThread()
+//
+//
+//
+:iIsAvailable(EFalse),iIsSync(EFalse),iThread(NULL),iId(0),iIsHung(EFalse),iMediaChangePending(EFalse)
+	{
+	TInt r=iFSLock.CreateLocal();
+	__ASSERT_ALWAYS(r==KErrNone,Fault(EFsThreadConstructor));
+	}
+
+TFsPluginThread::TFsPluginThread()
+//
+//
+//
+:iIsAvailable(ETrue),iThread(NULL),iId(0)
+	{
+	TInt r=iPluginLock.CreateLocal();
+	iDriveNumber= KMaxDrives+1;
+	__ASSERT_ALWAYS(r==KErrNone,Fault(EFsThreadConstructor));
+	}
+
+TInt FsThreadManager::CreateDisconnectThread()
+//
+// Called just once at startup
+//
+	{
+	__PRINT(_L("Create disconnect thread"));
+	TRAPD(r,iDisconnectThread=CDisconnectThread::NewL());
+	if(r!=KErrNone)
+		return(r);
+	TRAP(r,iDisconnectThreadId=iDisconnectThread->StartL());
+	if(r!=KErrNone)
+		{
+		delete(iDisconnectThread);
+		iDisconnectThread=NULL;
+		iDisconnectThreadId=0;
+		}
+	__THRD_PRINT2(_L("iDisconnectThread=0x%x id=0x%x"),iDisconnectThread,iDisconnectThreadId);
+	return(r);
+	}
+
+TBool FsThreadManager::IsDisconnectThread()
+//
+// Return ETrue if the calling thread is the disconnect thread
+//
+	{
+	return(iDisconnectThreadId==RThread().Id());
+	}
+
+
+TInt FsThreadManager::InitDrive(TInt aDrvNumber,TBool aIsSync)
+//
+// Create a drive thread
+// Should only by called from main file server thread with drive thread unavailable
+// 
+//
+	{
+	__PRINT1(_L("FsThreadManager::InitDrive() drive=%d"),aDrvNumber);
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+	__ASSERT_ALWAYS(!t.iIsAvailable,Fault(EThreadManagerInitDrive));
+	t.iIsSync=ETrue;
+	
+	return ChangeSync(aDrvNumber, aIsSync);
+	}
+
+
+TInt FsThreadManager::ChangeSync(TInt aDrvNumber,TBool aIsSync)
+//
+// Change if a drive is syncronouse after it has been inishalised.
+// Should be called from the main thread.  
+// Any pending oporations will be compleated.
+//
+
+	{
+	__PRINT1(_L("FsThreadManager::ChangeSync() drive=%d"),aDrvNumber);
+	__CHECK_DRVNUM(aDrvNumber);
+	__CHECK_MAINTHREAD();
+
+	LockDrive(aDrvNumber);
+	TFsDriveThread& t=FsThreadManager::GetFsDriveThread(aDrvNumber);
+	TInt r=KErrNone;
+
+	if (aIsSync!=t.iIsSync)
+		{
+		if (!aIsSync)
+			{
+			if(!t.iThread)
+				{
+				TRAP(r,t.iThread=CDriveThread::NewL());
+				if(r!=KErrNone)
+					{
+					UnlockDrive(aDrvNumber);
+					return(r);
+					}
+				}
+			TRAP(r,t.iId=t.iThread->StartL(aDrvNumber));
+			__THRD_PRINT2(_L("Starting thread 0x%x returned %d"),&t,r);
+			if(r!=KErrNone)
+				aIsSync=ETrue;
+			else
+				{
+				t.iIsSync=EFalse;
+				__THRD_PRINT1(_L("drive thread id=0x%x"),t.iId);
+				}
+			}
+		if (aIsSync)
+			{
+			if (t.iThread)
+				{
+				t.iThread->CompleteAllRequests(KErrNotReady);
+				t.iThread->iExit=ETrue;
+				t.iThread=NULL;
+				}
+			t.iIsSync=ETrue;
+			}
+		}
+	if (r==KErrNone)
+		t.iIsAvailable=ETrue;
+	
+	UnlockDrive(aDrvNumber);	
+	return r;
+	}
+
+void FsThreadManager::CloseDrive(TInt aDrvNumber)
+//
+// Close a drive thread
+// Assumes already locked or safe
+// If file system in not synchronous then should be called from a drive thread request
+//
+	{
+	__PRINT1(_L("FsThreadManager::CloseDrive() drive=%d"),aDrvNumber);
+	__CHECK_DRVNUM(aDrvNumber);
+
+	// no need to cancel requests if synchronous since queued
+	if(!FsThreadManager::IsDriveSync(aDrvNumber,EFalse))
+		{
+		CDriveThread* pT=NULL;
+		TInt r=FsThreadManager::GetDriveThread(aDrvNumber,&pT);
+		__ASSERT_ALWAYS(r==KErrNone && pT,Fault(EDismountFsDriveThread));
+		pT->CompleteAllRequests(KErrNotReady);
+		}
+
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+	__ASSERT_ALWAYS(t.iIsAvailable,Fault(EFsThreadDriveClose1));
+	if(!t.iIsSync)
+		{
+		__ASSERT_ALWAYS(FsThreadManager::IsDriveThread(aDrvNumber,EFalse),Fault(EFsThreadDriveClose2));
+		
+		StopFinalisationTimer(aDrvNumber);
+
+		// drive thread will exit when request completed
+		t.iThread->iExit=ETrue;
+
+		// Ensure that subsequent remounts use a new thread AND a new CDriveThread object - 
+		// re-use of the CDriveThread object can lead to deadlock while both old & new threads are active.
+		t.iThread = NULL;	
+
+		// Empty the closed file queue for this drive before the thread ends and the CDriveThread object 
+		// is deleted because the closed file queue contains a list of CFileCache objects which will 
+		// call CRequestThread::RemoveTimer() when closed
+		TClosedFileUtils::Remove(aDrvNumber);
+		}
+	else
+		{
+		__CHECK_MAINTHREAD();
+		t.iIsSync=EFalse;
+		}
+	t.iIsAvailable=EFalse;
+	t.iId=0;
+	}
+
+
+TBool FsThreadManager::IsDriveAvailable(TInt aDrvNumber,TBool aIsLock)
+//
+//
+//
+	{
+	__CHECK_DRVNUM(aDrvNumber);
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+	if(aIsLock)
+		t.iFSLock.Wait();
+	TBool b=t.iIsAvailable;
+	if(aIsLock)
+		t.iFSLock.Signal();
+	__THRD_PRINT2(_L("drive thread %d iIsAvailable=%d"),aDrvNumber,b);
+	return(b);
+	}
+
+TBool FsThreadManager::IsDriveSync(TInt aDrvNumber,TBool aLock)
+//
+// 
+//
+	{
+	__CHECK_DRVNUM(aDrvNumber);
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+	if(aLock)
+		t.iFSLock.Wait();
+	TBool b=(t.iIsAvailable&&t.iIsSync);
+	if(aLock)
+		t.iFSLock.Signal();
+	__THRD_PRINT2(_L("drive thread %d iIsSync=%d"),aDrvNumber,b);
+	return(b);
+	}
+
+TInt FsThreadManager::GetDriveThread(TInt aDrvNumber, CDriveThread** aDrvThread)
+//
+// Assumes locked or called from the drive thread
+//
+	{
+	__CHECK_DRVNUM(aDrvNumber);
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+	*aDrvThread=NULL;
+	TInt r=KErrNone;
+	if(!t.iIsAvailable)
+		r=KErrNotReady;
+	else if(t.iIsSync)
+		r=KErrAccessDenied;
+	else
+		{
+		*aDrvThread=t.iThread;
+		__ASSERT_DEBUG(*aDrvThread,Fault(EFsThreadGetThread));
+		}
+	__THRD_PRINT4(_L("GetDriveThread(%d) r %d id=0x%x *aDrvThread=0x%x"),aDrvNumber, r, t.iId, *aDrvThread);
+	return r;
+	}
+
+
+void FsThreadManager::LockDrive(TInt aDrvNumber)
+//
+// Lock the TFsDriveThread object for the aDrvNumber drive
+//
+	{
+	__CHECK_DRVNUM(aDrvNumber);
+	__THRD_PRINT1(_L("FsThreadManager::LockDrive(%d)"),aDrvNumber);
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+	t.iFSLock.Wait();
+	}
+
+void FsThreadManager::UnlockDrive(TInt aDrvNumber)
+//
+// Unlock the TFsDriveThread object for the aDrvNumber drive
+//
+	{
+	__CHECK_DRVNUM(aDrvNumber);
+	__THRD_PRINT1(_L("FsThreadManager::UnlockDrive(%d)"),aDrvNumber);
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+	t.iFSLock.Signal();
+	}
+
+void FsThreadManager::SetDriveHung(TInt aDrvNumber, TBool aIsHung)
+	{
+	if (aDrvNumber < EDriveA || aDrvNumber > EDriveZ)
+		return;
+
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+
+	// quick exit if hung state not changing or drive thread not available
+	if ((!t.iIsAvailable) || (t.iIsHung == aIsHung))
+		return;
+
+	t.iFSLock.Wait();
+
+	// Don't clear the hung state if this is a synchronous request
+	// and the drive is asynchronous - we need to wait for whatever 
+	// asynchronous request caused the hang to complete first.
+	TUint id=RThread().Id();
+	TBool isDriveThread = t.iIsSync || (!t.iIsSync && t.iId == id);
+	__THRD_PRINT3(_L("Set %d Hung %d. Is Drive thread %d"), aDrvNumber, aIsHung, isDriveThread);
+	if (!aIsHung && !isDriveThread)
+		{
+		t.iFSLock.Signal();
+		return;
+		}
+
+	t.iIsHung = aIsHung;
+
+	// if we're no longer hung, see if there's a media change pending
+	// and if so issue one now
+	TBool mediaChangePending = EFalse;
+	if(!aIsHung)
+		{
+		mediaChangePending = t.iMediaChangePending;
+		t.iMediaChangePending = EFalse;
+		}
+	t.iFSLock.Signal();
+
+	// If the drive is now hung we must complete all requests in the drive thread's
+	// queue - and all subsequent requests - with KErrNotReady to prevent deadlock.
+	// For example, the notifier server may try to access the loader but one of the
+	// requests in the queue may already belong to the loader.
+	if (aIsHung && t.iThread)
+		t.iThread->CompleteClientRequests(KErrNotReady);
+
+	if(mediaChangePending)
+		FsNotify::DiskChange(aDrvNumber);
+	}
+
+
+TBool FsThreadManager::IsDriveHung(TInt aDrvNumber)
+	{
+	if (aDrvNumber < EDriveA || aDrvNumber > EDriveZ)
+		return EFalse;
+
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+//	__THRD_PRINT3(_L("Is %d Hung = %d"), aDrvNumber, t.iIsHung);
+	return t.iIsHung;
+	}
+
+
+// If the drive is hung, then don't complete any disk change 
+// notifications until the request causing the hang completes.
+void FsThreadManager::SetMediaChangePending(TInt aDrvNumber)
+	{
+	if (aDrvNumber < EDriveA || aDrvNumber > EDriveZ)
+		return;
+
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+
+	if (!t.iIsAvailable)
+		return;
+
+	t.iFSLock.Wait();
+	t.iMediaChangePending = ETrue;
+	t.iFSLock.Signal();
+	}
+
+void FsThreadManager::SetMainThreadId()
+//
+// called at file server startup, assumes called from main file server thread
+//
+	{
+	iMainId=RThread().Id();
+	__THRD_PRINT1(_L("Main thread id = 0x%x"),iMainId);
+	}
+
+TBool FsThreadManager::IsDriveThread(TInt aDrvNumber,TBool aIsLock)
+//
+// Return ETrue if the calling thread is the aDrvNumber drive thread
+//
+	{
+	__CHECK_DRVNUM(aDrvNumber);
+	TFsDriveThread& t=GetFsDriveThread(aDrvNumber);
+	TUint id=RThread().Id();
+	if(aIsLock)
+		t.iFSLock.Wait();
+	TBool b = t.iIsAvailable && (!t.iIsSync && t.iId==id || t.iIsSync);
+	if(aIsLock)
+		t.iFSLock.Signal();
+	return(b);
+	}
+	
+TBool FsThreadManager::IsMainThread()
+//
+// Returns ETrue if calling thread is same as main file server thread
+//
+	{
+	return((TUint)(RThread().Id())==iMainId);
+	}
+
+
+void FsThreadManager::StartFinalisationTimer(TInt aDrvNumber)
+	{
+	if (aDrvNumber < EDriveA || aDrvNumber > EDriveZ)
+		return;
+
+	// If the message could cause disk modification, make sure that the finalisation
+	// timer is queued so that we can mark the disk consistent at some point in the future
+	CDriveThread* driveThread=NULL;
+	TInt r = GetDriveThread(aDrvNumber, &driveThread);
+	if(r == KErrNone && driveThread != NULL)
+		driveThread->StartFinalisationTimer();
+	}
+
+void FsThreadManager::StopFinalisationTimer(TInt aDrvNumber)
+	{
+	if (aDrvNumber < EDriveA || aDrvNumber > EDriveZ)
+		return;
+
+	// If the message could cause disk modification, make sure that the finalisation
+	// timer is queued so that we can mark the disk consistent at some point in the future
+	CDriveThread* dT=NULL;
+	TInt r = GetDriveThread(aDrvNumber, &dT);
+	if(r == KErrNone && dT != NULL)
+		{
+		dT->StopFinalisationTimer();
+		}
+	}
+
+CRequestThread::CRequestThread()
+//
+//
+//
+:iList(_FOFF(CFsRequest,iLink))
+	{
+	//iRequest=NULL;
+	//iIsWaiting=EFalse;
+	iExit=EFalse;
+	}
+
+TInt CRequestThread::Initialise()
+//
+// Initialise
+//
+	{
+	TInt r=iListLock.CreateLocal();
+	return(r);
+	}
+
+CRequestThread::~CRequestThread()
+//
+//
+//
+	{
+	__ASSERT_ALWAYS(iList.IsEmpty(),Fault(ERequestThreadDestructor));
+	iListLock.Close();
+
+	if(iThread.Handle() != 0)
+		{
+		iThread.Close();
+		}
+	delete iTimer;
+	}
+
+LOCAL_C TInt ThreadFunction(TAny* aPtr)
+//
+//
+//
+	{
+	__THRD_PRINT(_L("ThreadFunction()"));
+	User::SetCritical(User::ESystemCritical);
+	CRequestThread* pT=(CRequestThread*)aPtr;
+	TInt r = pT->ThreadFunction();
+	delete pT;
+	return r;
+	}
+
+void CRequestThread::CompleteAllRequests(TInt aValue)
+    {
+    __THRD_PRINT(_L("CRequestThread::CompleteAllRequests()"));
+    iListLock.Wait();
+    while(!iList.IsEmpty())
+        {
+        CFsRequest* pR=iList.First();
+        pR->iLink.Deque();
+        iListLock.Signal();
+        pR->Complete(aValue);
+        iListLock.Wait();
+        }
+    iListLock.Signal();
+    __THRD_PRINT(_L("all requests completed"));
+    }
+
+TInt CRequestThread::ThreadFunction()
+//
+// entry point for the thread
+//
+	{
+	iTimer = CFsDeltaTimer::New(*this, EPriorityLess);
+	if (iTimer == NULL)
+		{
+		RThread::Rendezvous(KErrNoMemory);
+		return(KErrNone);
+		}
+	iTimer->iStatus = KErrNotReady;
+
+	CTrapCleanup* trapHandler=CTrapCleanup::New();
+	if (trapHandler==NULL)
+		{
+		RThread::Rendezvous(KErrNoMemory);
+		delete iTimer;
+		return(KErrNone);
+		}
+
+	RThread::Rendezvous(KErrNone);
+
+	TInt err = DoThreadInitialise();
+	if(err != KErrNone)
+		{
+		delete trapHandler;
+		return(KErrNone);
+		}
+
+	iExit=EFalse;
+	iIsWaiting=EFalse;
+	// start receiving
+	Receive();
+	CompleteAllRequests(KErrNotReady);
+	
+	delete trapHandler;
+	return(KErrNone);
+	}
+
+TInt CRequestThread::DoThreadInitialise()
+	{
+	return KErrNone;
+	}
+
+TInt CRequestThread::DoStart(RThread& aThread)
+//
+// create thread and return handle
+// necessary for client to close thread handle if successful
+//
+	{
+	TInt r=aThread.Create(KNullDesC,::ThreadFunction,KRequestThreadStackSize,NULL,(TAny*)this);
+	__PRINT1(_L("CRequestThread::DoStart() r=%d"),r);
+	if(r!=KErrNone)
+		return(r);
+	TRequestStatus status;
+	aThread.Rendezvous(status);
+	if(status==KRequestPending)
+		{
+		aThread.SetPriority(EPriorityLess);
+		aThread.Resume();
+		}
+	else
+		{
+		aThread.Kill(0);
+		}
+	User::WaitForRequest(status);
+	r = status.Int();
+	if(r!=KErrNone)
+		aThread.Close();
+	else
+		iThread = aThread;
+
+	return(r);
+	}
+	
+
+void CRequestThread::Receive()
+//
+// Receive and process requests
+//
+	{
+	FOREVER
+		{
+		iListLock.Wait();
+		if(!iList.IsEmpty())
+			{
+			iRequest=iList.First();
+			iRequest->iLink.Deque();
+			__THRD_PRINT(_L("CRequestThread::Receive() dequeing"));
+			iListLock.Signal();
+			}
+		else
+			{
+			iIsWaiting=ETrue;
+			iRequest = NULL;	// set to NULL so we can distinguish between a timer and a request signal
+			iListLock.Signal();
+			__THRD_PRINT(_L("CRequestThread::Receive() waiting"));
+			User::WaitForAnyRequest();
+			iIsWaiting=EFalse;	// force main thread to post new requests on queue to avoid suspending this thread unnecessarily
+			}
+		__THRD_PRINT2(_L("received req 0x%x, func 0x%x"),iRequest, iRequest ? iRequest->Operation()->iFunction : -1);
+
+		iTimer->RunL();
+
+		if (iRequest)
+			iRequest->Process();
+
+		if(iExit)
+			break;
+		}
+
+	}
+
+void CRequestThread::Deliver(CFsRequest* aRequest,TBool aIsFront, TBool aLowPriority)
+//
+// Deliver a request to the list from calling thread
+// Write request directly to current request if thread is waiting
+//
+	{
+	__THRD_PRINT4(_L("Deliver req %08x to threadId %lx aIsFront=%d iIsWaiting=%d"), aRequest, iThread.Id().Id(), aIsFront, iIsWaiting);
+	iListLock.Wait();
+	if (iList.IsEmpty())
+		{
+		// if this is a low priority request (and this is the only request in the queue),
+		// reduce the thread's priority to EPriorityAbsoluteBackground
+		if (iLowPriority != aLowPriority)
+			{
+			__THRD_PRINT(_L("LOWERING THREAD PRIORITY"));
+			iThread.SetPriority(aLowPriority?EPriorityAbsoluteBackground:EPriorityLess);
+			iLowPriority = aLowPriority;
+			}
+		}
+	else
+		{
+		// there's more than one request in the queue, so rather than go throught the entire queue
+		// to determine what the thread's priority should be, assume that it should be "high"
+		if (iLowPriority)
+			{
+			iThread.SetPriority(EPriorityLess);
+			iLowPriority = EFalse;
+			}
+		}
+
+	if(iIsWaiting)
+		{
+		// the request thread must be waiting on the iWaitLock
+		iIsWaiting=EFalse;
+		iListLock.Signal();
+		iRequest=aRequest;
+
+		iThread.RequestSignal();
+		}
+	else
+		{
+		if(aIsFront)
+			iList.AddFirst(*aRequest);
+		else
+			iList.AddLast(*aRequest);
+		iListLock.Signal();
+		}
+
+
+	}
+
+void CRequestThread::DeliverFront(CFsRequest* aRequest)
+//
+//
+//
+	{
+	Deliver(aRequest,ETrue);
+	}
+
+void CRequestThread::DeliverBack(CFsRequest* aRequest, TBool aLowPriority)
+//
+//
+//
+	{
+	Deliver(aRequest,EFalse,aLowPriority);
+	}
+
+
+
+CFsDeltaTimer* CRequestThread::Timer()
+	{
+	__ASSERT_ALWAYS(iTimer,Fault(ERequestThreadNotInitialised));
+	return iTimer;
+	}
+
+
+CDriveThread::CDriveThread()
+	: iFinaliseTimer(FinaliseTimerEvent, this)
+	{
+	}
+
+CDriveThread* CDriveThread::NewL()
+//
+//
+//
+	{
+	__PRINT(_L("CDriveThread::NewL()"));
+	CDriveThread* pT=new(ELeave) CDriveThread;
+	TInt r=pT->Initialise();
+	if(r!=KErrNone)
+		{
+		delete(pT);
+		User::Leave(r);
+		}
+	return(pT);
+	}
+
+TUint CDriveThread::StartL(TInt aDrvNumber)
+//
+//
+//
+	{
+	__PRINT1(_L("CDriveThread::StartL() on drive %d"),aDrvNumber);
+	iDriveNumber=aDrvNumber;
+	RThread t;
+	User::LeaveIfError(DoStart(t));
+	TUint id=t.Id();
+	return(id);
+	}
+
+TInt CDriveThread::DoThreadInitialise()
+//
+// Initialise function for the drive thread
+//  - Renames the thread to contain the drive number.
+//  - Note: Drive mappings are not available at this time, so we can't show the actual drive letter.
+//
+	{
+	__PRINT1(_L("CDriveThread::DoThreadInitialise() on drive %d"), iDriveNumber);
+	
+	TBuf<16> name;
+	name.Format(_L("DriveThread_%02d"), iDriveNumber);
+	return(RThread::RenameMe(name));
+	}
+
+void CDriveThread::CompleteSessionRequests(CSessionFs* aSession, TInt aValue)
+//
+//
+//
+	{
+	__THRD_PRINT1(_L("CDriveThread::CompleteSessionReqeusts() drive=%d"),iDriveNumber);
+	iListLock.Wait();
+	TDblQueIter<CFsRequest> q(iList);
+	CFsRequest* pR;
+	while((pR=q++)!=NULL)
+		{
+		if(pR->Session()==aSession)
+			{
+			pR->iLink.Deque();
+			iListLock.Signal();
+			pR->Complete(aValue);
+			iListLock.Wait();
+			// set iterator back to head of queue in case Complete() has itself removed requests from the queue
+			q.SetToFirst();
+			}
+		}
+	iListLock.Signal();
+	__THRD_PRINT(_L("session requests completed"));
+	}
+
+
+void CDriveThread::CompleteReadWriteRequests()
+	{
+	__THRD_PRINT1(_L("CDriveThread::CompleteReadWriteRequests() drive=%d"),iDriveNumber);
+
+	iListLock.Wait();
+
+	TDblQueIter<CFsRequest> q(iList);
+	CFsRequest* pR;
+	while((pR=q++)!=NULL)
+		{
+		TInt func = pR->Operation()->Function();
+		if (func == EFsFileRead || func == EFsFileWrite || func == EFsFileWriteDirty)
+			{
+			pR->iLink.Deque();
+			pR->Complete(KErrNotReady);
+			}
+		}
+	iListLock.Signal();
+
+	__THRD_PRINT(_L("file read/write requests completed"));
+	}
+
+/*
+This function is called by FsThreadManager::SetDriveHung() and attempts to purge the request queue 
+of all requests which MIGHT belong to the critical notifier server (or to the loader) to avoid causing 
+a deadlock when calling the server.
+
+All requests are completed with KErrNotReady apart from :
+- EFsFileWriteDirty requests, to avoid losing dirty data
+- KDispatchObjectClose requests as they are raised by the file server only and therefore cannot belong to the critical notifier server
+- EFsFileSubClose requests, to avoid closing files containing dirty data. These requests have their message completed
+  so that clients are unblocked, but the request itself is not processed until later. (If the request WAS processed
+  and completed, then this might result in the CFileCB and CMountCB object being deleted, leading to problems 
+  dereferencing invalid pointers).
+*/
+void CDriveThread::CompleteClientRequests(TInt aValue)
+	{
+	__THRD_PRINT1(_L("CDriveThread::CompleteClientRequests() drive=%d"),iDriveNumber);
+
+	iListLock.Wait();
+
+	TDblQueIter<CFsRequest> q(iList);
+	CFsRequest* pR;
+	while((pR=q++)!=NULL)
+		{
+		TInt func = pR->Operation()->Function();
+		if(func == EFsFileSubClose)
+			{
+			TInt msgHandle = pR->Message().Handle();
+			if ((msgHandle != KLocalMessageHandle) && (msgHandle != 0))
+				pR->Message().Complete(KErrNone);
+			}
+		else if (func != EFsFileWriteDirty && func != KDispatchObjectClose)
+			{
+			pR->iLink.Deque();
+			iListLock.Signal();
+			pR->Complete(aValue);
+			iListLock.Wait();
+			}
+		}
+	iListLock.Signal();
+
+	__THRD_PRINT(_L("client read requests completed"));
+	}
+
+TBool CDriveThread::IsRequestWriteable()
+//
+// return if current request may cause write to disk
+// must be called from drive thread
+//
+	{
+	__ASSERT_ALWAYS(FsThreadManager::IsDriveThread(iDriveNumber,EFalse),Fault(EDriveThreadWriteable));
+	return(iRequest->Operation()->IsWrite());
+	}
+
+TBool CDriveThread::IsSessionNotifyUser()
+//
+// return if request's session has notify user set
+// must be called from drive thread and request have a session set
+//
+	{
+	__ASSERT_ALWAYS(FsThreadManager::IsDriveThread(iDriveNumber,EFalse),Fault(EDriveThreadNotifyUser1));
+	// NB For read-ahead or a flush-dirty write request generated by the file cache, the session will be NULL: 
+	// in this case assume that notify user is set (as it's the safest option)
+	CSessionFs* session = iRequest->Session();
+	return session ?  session->GetNotifyUser() : ETrue;
+	}
+
+void CDriveThread::StartFinalisationTimer()
+	{
+	if(IsProxyDrive(iDriveNumber))
+		iFinaliseTimer.Start(this, KFinaliseTimerPeriod);
+	}
+	
+
+void CDriveThread::StopFinalisationTimer()
+	{
+	iFinaliseTimer.Stop();
+	}
+
+TInt CDriveThread::FinaliseTimerEvent(TAny* aSelfP)
+	{
+	CDriveThread& self = *(CDriveThread*)aSelfP;
+
+	TDrive& drive = TheDrives[self.iDriveNumber];
+	if(drive.IsMounted() && drive.CurrentMount().LockStatus() == 0)
+		{
+		// Ignore the error here, as there's nothing we can do about it...
+		(void)drive.FinaliseMount(RFs::EFinal_RW);
+		}
+
+	return KErrNone;
+	}
+
+
+CDisconnectThread::~CDisconnectThread()
+//
+//
+//
+	{
+	if(iRequest)
+		delete(iRequest);
+	}
+
+
+CDisconnectThread* CDisconnectThread::NewL()
+//
+//
+//
+	{
+	__THRD_PRINT(_L("CDisconnectThread::NewL()"));
+	CDisconnectThread* pT=new(ELeave) CDisconnectThread;
+	TInt r=pT->Initialise();
+	if(r!=KErrNone)
+		{
+		delete(pT);
+		User::Leave(r);
+		}
+	return(pT);
+	}
+
+TUint CDisconnectThread::StartL()
+//
+//
+//
+	{
+	__PRINT(_L("CDisconnectThread::StartL()"));
+	iRequest = new(ELeave) CFsInternalRequest;
+	__THRD_PRINT1(_L("internal request = 0x%x"),iRequest);
+	iRequest->Set(CancelSessionOp,NULL);	
+	
+	RThread t;
+	TInt r=DoStart(t);
+	if(r!=KErrNone)
+		{
+		delete(iRequest);
+		iRequest=NULL;
+		User::Leave(r);
+		}
+	iRequest->SetThreadHandle(t.Handle());
+	__THRD_PRINT1(_L("CDisconnect::StartL() handle=%d"),t.Handle());
+	iRequest->SetAllocated();
+	TUint id=t.Id();
+	return(id);
+	}
+
+
+CPluginThread::CPluginThread(CFsPlugin& aPlugin)
+  : iPlugin(aPlugin)
+	{
+	/** @prototype */
+	iOperationLock.Close();
+	}
+
+
+CPluginThread* CPluginThread::NewL(CFsPlugin& aPlugin)
+	{
+	__PRINT(_L("CPluginThread::NewL()"));
+	CPluginThread* pT=new(ELeave) CPluginThread(aPlugin);
+	TInt r=pT->Initialise();
+
+	/** @prototype */
+	if(r == KErrNone)
+		r=pT->iOperationLock.CreateLocal(0);
+
+	if(r!=KErrNone)
+		{
+		delete(pT);
+		User::Leave(r);
+		}
+	return(pT);
+	}
+
+TUint CPluginThread::StartL()
+	{
+	__PRINT(_L("CPluginThread::StartL()"));
+	RThread t;
+	User::LeaveIfError(DoStart(t));
+	TUint id=t.Id();
+	return(id);
+	}
+
+void CPluginThread::CompleteSessionRequests(CSessionFs* aSession, TInt aValue)
+	{
+	__THRD_PRINT(_L("CPluginThread::CompleteSessionReqeusts()"));
+	iListLock.Wait();
+	TDblQueIter<CFsRequest> q(iList);
+	CFsRequest* pR;
+	while((pR=q++)!=NULL)
+		{
+		if(pR->Session()==aSession)
+			{
+			pR->iLink.Deque();
+			pR->Complete(aValue);
+			}
+		}
+	iListLock.Signal();
+	__THRD_PRINT(_L("session requests completed"));
+	}
+
+TInt CPluginThread::DoThreadInitialise()
+	{
+	__PRINT(_L("CPluginThread::DoThreadInitialise()"));
+ 	TRAPD(err, iPlugin.InitialiseL());
+
+	return err;
+	}
+
+/** @prototype */
+void CPluginThread::OperationLockWait()
+	{
+	iOperationLock.Wait();
+	}
+
+/** @prototype */
+void CPluginThread::OperationLockSignal()
+	{
+	iOperationLock.Signal();
+	}
+
+// Class TTickCountQue
+/**
+@internalComponent
+@released
+
+Constructs an empty list header
+*/
+TTickCountQue::TTickCountQue()
+	{}
+
+
+
+
+/**
+@internalComponent
+@released
+
+Adds the specified list element.
+
+The element is added into the list in order of its tick count.
+
+@param aRef The list element to be inserted.
+*/
+void TTickCountQue::Add(TTickCountQueLink& aRef)
+	{
+	TTickCountQueLink* currentLink = (TTickCountQueLink*)(iHead.iNext);
+	TTickCountQueLink* addLink = &aRef;
+
+	while (	(currentLink != (TTickCountQueLink*)&iHead) &&
+			(((TInt)(addLink->iTickCount - currentLink->iTickCount)) >= 0)
+		)
+		{
+		currentLink = (TTickCountQueLink*)currentLink->iNext;
+		}
+
+	addLink->Enque(currentLink->iPrev);
+	}
+
+
+
+
+/**
+@internalComponent
+@released
+
+Removes the first list element from the linked list if its tick count
+is prior to the current tick count.
+
+@param aTickCount The current tick count.
+
+@return A pointer to the element removed from the linked list. This is NULL 
+        if the first element has yet to expire or the queue is empty.
+*/
+TTickCountQueLink* TTickCountQue::RemoveFirst(TUint aTickCount)
+	{
+	TTickCountQueLink* firstLink = (TTickCountQueLink*)iHead.iNext;
+
+	if (((TInt)(firstLink->iTickCount - aTickCount)) <= 0)
+		{
+		return RemoveFirst();
+		}
+	else
+		{
+		return NULL;
+		}
+	}
+
+
+/**
+@internalComponent
+@released
+
+Removes the first list element from the linked list, if any.
+
+@return A pointer to the element removed from the linked list. This is NULL, 
+        if the queue is empty.
+*/
+TTickCountQueLink* TTickCountQue::RemoveFirst()
+	{
+	TTickCountQueLink* firstLink = (TTickCountQueLink*)iHead.iNext;
+
+	if (firstLink != (TTickCountQueLink*)&iHead)
+		{
+		firstLink->Deque();
+		return firstLink;
+		}
+
+	return NULL;
+	}
+
+
+
+
+/**
+@internalComponent
+@released
+
+Gets a pointer to the first list element in the doubly linked list.
+
+@return A pointer to the first list element in the doubly linked list. If 
+        the list is empty, this pointer is not necessarily NULL and must not
+		be assumed to point to a valid object.
+*/
+TTickCountQueLink* TTickCountQue::First() const
+	{
+#if defined (_DEBUG)
+	__DbgTestEmpty();
+#endif
+    return((TTickCountQueLink*)iHead.iNext);
+    }
+
+
+
+
+
+CFsDeltaTimer* CFsDeltaTimer::New(CRequestThread& aRequestThread, TInt aPriority)
+	{
+	TTimeIntervalMicroSeconds32 tickPeriod;
+	UserHal::TickPeriod(tickPeriod);
+
+	CFsDeltaTimer* timer = new CFsDeltaTimer(aRequestThread, aPriority, tickPeriod.Int());
+	if (timer == NULL)
+		return NULL;
+
+	if (timer->iTimer.CreateLocal() != KErrNone || 
+		timer->iLock.CreateLocal() != KErrNone)
+		{
+		delete timer;
+		return NULL;
+		}
+
+	return timer;
+	}
+
+CFsDeltaTimer::CFsDeltaTimer(CRequestThread& aRequestThread, TInt /*aPriority*/, TInt aTickPeriod) : 
+	iRequestThread(aRequestThread), iTickPeriod(aTickPeriod)
+	{
+	iThreadId = RThread().Id(); 
+	}
+
+/**
+Destructor.
+
+Frees all resources before destruction of the object. Specifically, it cancels 
+any outstanding timer requests generated by the RTimer object and then deletes 
+all timed event entries from the timed event queue.
+
+@see RTimer
+
+@publishedAll
+@released
+*/
+CFsDeltaTimer::~CFsDeltaTimer()
+	{
+	Cancel();
+
+	while (!iQueue.IsEmpty())
+		{
+		iQueue.First()->Deque();
+		}
+
+	iLock.Close();
+	iTimer.Close();
+	}
+
+
+/**
+Start the timer.
+
+@see RTimer
+
+@publishedAll
+@released
+*/
+void CFsDeltaTimer::Start(TThreadTimer& aEntry, TTimeIntervalMicroSeconds32 aTime)
+	{
+	iLock.Wait();
+
+	// must be already running on this thread or not running at all
+	ASSERT(aEntry.iRequestThread == NULL || aEntry.iRequestThread  == &iRequestThread);
+
+	// attach the entry to this thread
+	aEntry.iRequestThread = &iRequestThread;
+
+	// Remove the entry from the list (if it's already queued) 
+	// and then add it again in the correct order
+	aEntry.iLink.Deque();
+	QueueLong(TTimeIntervalMicroSeconds(MAKE_TINT64(0, aTime.Int())), aEntry);
+
+	iLock.Signal();
+	}
+
+void CFsDeltaTimer::Stop(TThreadTimer& aEntry)
+	{
+	iLock.Wait();
+
+	aEntry.iLink.Deque();
+	aEntry.iRequestThread = NULL;
+
+	iLock.Signal();
+	}
+
+
+TInt CFsDeltaTimer::QueueLong(TTimeIntervalMicroSeconds aTimeInMicroSeconds, TThreadTimer& aEntry)
+	{
+	const TInt64 timeInTicks = (aTimeInMicroSeconds.Int64() + iTickPeriod - 1) / iTickPeriod;
+
+	TInt timeInTicks32 = I64LOW(timeInTicks);
+
+	// We are using deltas on tick values, hence using maximum signed number of ticks
+	if (I64HIGH(timeInTicks) || (timeInTicks32 < 0))
+		{
+		return KErrOverflow;
+		}
+
+	// Make sure we queue for at least one tick
+	if (timeInTicks32 == 0)
+		{
+		timeInTicks32 = 1;
+		}
+	
+	// Calculate tick count for new entry
+	aEntry.iLink.iTickCount = User::TickCount() + timeInTicks32;
+
+	// Add this entry at the right spot
+	iQueue.Add(aEntry.iLink);
+
+	// we only need to re-start the timer if we've added an entry to the head of the queue
+	// or the timer is not already running
+	if (&aEntry.iLink == iQueue.First() || iStatus == KRequestPending)
+		Activate();
+	
+	return KErrNone;
+	}
+
+void CFsDeltaTimer::Activate()
+//
+// Queue a request on the timer.
+//
+	{
+	if (RThread().Id() != iThreadId)
+		{
+		iRestartNeeded = ETrue;
+		iRequestThread.iThread.RequestSignal();
+		return;
+		}
+
+	if (iStatus == KRequestPending)
+		Cancel();
+
+	if (!iQueue.IsEmpty() && !iQueueBusy)
+		{
+		const TInt ticksToWait = iQueue.First()->iTickCount - User::TickCount();
+
+		if (ticksToWait > 0)
+			{
+			iTimer.AfterTicks(iStatus, ticksToWait);
+			}
+		else
+			{
+			TRequestStatus* status = &iStatus;
+			User::RequestComplete(status, KErrNone);
+			}
+		}
+	}
+
+
+
+void CFsDeltaTimer::RunL()
+//
+// Call all zero delta callbacks
+	{
+	// if still running and no restart needed, then there's nothing to do
+	if (iStatus == KRequestPending && !iRestartNeeded)
+		return;
+
+
+	iLock.Wait();
+
+	// Queue busy
+	iQueueBusy = ETrue;
+
+	// Whilst the list of expired timers is being processed, time will pass and
+	// the tick count may have increased such that there are now more expired
+	// timers.  Loop until we have either emptied the queue or can wait for a
+	// timer exipration in the future.
+	if (iStatus == KErrNone)
+		{
+		iStatus = KErrNotReady;
+		while (!iQueue.IsEmpty())
+			{
+			// Calculate how long till first timer expires
+			const TUint tickCount = User::TickCount();
+
+			// If the first timer is yet to expire, wait some more
+			if (((TInt)(iQueue.First()->iTickCount - tickCount)) > 0)
+				{
+				break;
+				}
+
+			// Remove entry before callback to prevent re-entrancy issues
+			TTickCountQueLink* entry = iQueue.RemoveFirst();
+
+			// Iterate through the timers we know have expired based on the
+			// last calculation of delta
+			while (entry)
+				{
+				TThreadTimer* threadTimer = reinterpret_cast<TThreadTimer*>(PtrSub(entry, _FOFF(TThreadTimer, iLink)));
+				threadTimer->iRequestThread = NULL;	// indicate timer not running
+
+				// Make callback.  This could go reentrant on Queue[Long]() or Remove().
+				iLock.Signal();
+				threadTimer->iCallBack.CallBack();
+				iLock.Wait();
+
+				// Remove the next expired entry, if any
+				entry = iQueue.RemoveFirst(tickCount);
+				}
+			}
+		}
+
+	// Queue idle
+	iQueueBusy = EFalse;
+
+
+	// Requeue timer if queue isn't empty
+	Activate();
+
+	iRestartNeeded = EFalse;
+
+	iLock.Signal();
+	}
+	
+void CFsDeltaTimer::Cancel()
+	{
+	if (iStatus == KRequestPending)
+		{
+		iTimer.Cancel();
+		User::WaitForRequest(iStatus);
+		}
+	}
+
+
+
+TThreadTimer::TThreadTimer(TInt (*aCallBackFunction)(TAny*),TAny* aPtr) :
+	iCallBack(aCallBackFunction, aPtr),
+	iRequestThread(NULL)
+	{
+	};	
+
+
+void TThreadTimer::Start(CRequestThread* aRequestThread, TTimeIntervalMicroSeconds32 aTime)
+	{
+	ASSERT(aRequestThread);
+
+	// NB: There are no locks here, so we have to be aware that CFsDeltaTimer::RunL()
+	// may be running in another thread and set iRequestThread to NULL
+	CRequestThread* requestThread = iRequestThread;
+	if (!requestThread)	// if not already running, use caller's request thread
+		requestThread = aRequestThread;
+
+
+	__ASSERT_DEBUG(requestThread->Timer(),Fault(ERequestThreadNotInitialised));
+	requestThread->Timer()->Start(*this, aTime);
+	}
+
+
+void TThreadTimer::Stop()
+	{
+	// NB: There are no locks here, so we have to be aware that CFsDeltaTimer::RunL()
+	// may be running in another thread and set iRequestThread to NULL
+	CRequestThread* requestThread = iRequestThread;
+	if (requestThread)
+		requestThread->Timer()->Stop(*this);
+	}
+
+