Unable to read same file in parallel

Windows specific forum
sepp
User
User
Posts: 10
Joined: Sat Jul 28, 2012 11:31 pm

Unable to read same file in parallel

Post by sepp »

Hello all,

I need to read a logfile in two different applications at the same time. As PB internal functions do not allow to set the Share-Mode I am using direct API-Calls. There I am using #FILE_SHARE_READ, but the second call always fails with error-code 32: ERROR_SHARING_VIOLATION: - The process cannot access the file because it is being used by another process.

Below is an example with 3 threads where one writes a logfile and two try to read them line by line. Does anyone know why the second read-thread fails? It should be possible to read the same file at the same time several times without problems.

When the writer-thread opens the file in modes #FILE_SHARE_WRITE or #FILE_SHARE_READ | #FILE_SHARE_WRITE the file can not be read by any of the two threads only when #FILE_SHARE_READ is used for the writer-thread and then it only works if the reader-threads use #FILE_SHARE_WRITE or #FILE_SHARE_READ | #FILE_SHARE_WRITE, not when using #FILE_SHARE_READ. Strange ...

Thanks

Code: Select all

XIncludeFile "File.pbi" ; https://pb-source-repositery.googlecode.com/svn/trunk/CodeArchiv_v4-Beta/WindowsAPI-Examples/FileHandling_WinAPI.pb

Declare API_FileCreateShared2(*File.API_FileHandle,File$)
Declare API_FileReadShared2(*File.API_FileHandle,File$)
Declare.s ShowError()
  
OpenConsole()

Global file.s = "test.txt"

Procedure WriteMe(number)
  Protected handle.API_FileHandle 
  PrintN("Starting Thread " + Str(number))
  If API_FileCreateShared2(handle,file)
    For i=1 To 40
      API_WriteStringN(handle,Str(i))
      PrintN("Thread " + Str(number) + ": " + Str(i))
      Delay(500)
    Next
  Else
    PrintN("Thread " + Str(number) + ": Can Not write To file: " + ShowError())
  EndIf
EndProcedure  

Procedure ReadMe(number)
  Protected handle.API_FileHandle 
  PrintN("Starting Thread " + Str(number))
  If API_FileReadShared2(handle,file)
    While API_EOF(handle)=#False 
      line.s = API_ReadString(handle)
      PrintN("Thread " + Str(number) + ": " + line.s)
      Delay(1000)
    Wend
    API_CloseFile(handle)
  Else
    PrintN("Thread " + Str(number) + ": Unable to open file: " + ShowError())
  EndIf 
EndProcedure

CreateThread(@WriteMe(), 0)
Delay(1000)
CreateThread(@ReadMe(), 1)
CreateThread(@ReadMe(), 2)
Delay(20000)


Procedure API_FileCreateShared2(*File.API_FileHandle,File$)
  ;*File\FHandle=CreateFile_(File$,#GENERIC_WRITE,#FILE_SHARE_READ Or #FILE_SHARE_WRITE,0,#CREATE_ALWAYS,#FILE_ATTRIBUTE_NORMAL,0)
  *File\FHandle=CreateFile_(File$,#GENERIC_WRITE,#FILE_SHARE_READ,0,#CREATE_ALWAYS,#FILE_ATTRIBUTE_NORMAL,0)
  ;*File\FHandle=CreateFile_(File$,#GENERIC_WRITE,#FILE_SHARE_WRITE,0,#CREATE_ALWAYS,#FILE_ATTRIBUTE_NORMAL,0)
  If *File\FHandle=#INVALID_HANDLE_VALUE
    ProcedureReturn #False
  Else
    ProcedureReturn #True
  EndIf
EndProcedure

Procedure API_FileReadShared2(*File.API_FileHandle,File$)
  *File\FHandle=CreateFile_(File$,#GENERIC_READ,#FILE_SHARE_READ Or #FILE_SHARE_WRITE,0,#OPEN_EXISTING,#FILE_ATTRIBUTE_NORMAL,0)
  ;*File\FHandle=CreateFile_(File$,#GENERIC_READ,#FILE_SHARE_READ,0,#OPEN_EXISTING,#FILE_ATTRIBUTE_NORMAL,0)
  ;*File\FHandle=CreateFile_(File$,#GENERIC_READ,#FILE_SHARE_WRITE,0,#OPEN_EXISTING,#FILE_ATTRIBUTE_NORMAL,0)  
  If *File\FHandle=#INVALID_HANDLE_VALUE
    ProcedureReturn #False
  Else
    *File\Buffer=GlobalAlloc_(#GMEM_FIXED|#GMEM_ZEROINIT,#API_File_BufferLen)
    *File\ReadPos=*File\Buffer
    *File\DataInBuffer=0
    ProcedureReturn #True
  EndIf
EndProcedure

Procedure.s ShowError()
  Protected Error,*MemoryID,e$,Length
  Error = GetLastError_()
  If Error
    *MemoryID = AllocateMemory(255)
    If *MemoryID
      Length = FormatMessage_ (#FORMAT_MESSAGE_FROM_SYSTEM, #Null, Error, 0, *MemoryID, 255, #Null)
      If Length > 1 ; Some error messages are "" + Chr (13) + Chr (10)... stoopid M$... :(
        e$ = PeekS(*MemoryID, Length - 2) + " (" + StrU(Error.i) + ")"
      Else
        e$ = "Unknown error!"
      EndIf
      FreeMemory(*MemoryID)
      ProcedureReturn e$
    Else
      ProcedureReturn "Error while decoding Error("+Str(Error)+")"
    EndIf
  Else
    ProcedureReturn "No error has occurred!"
  EndIf
EndProcedure
User avatar
RichAlgeni
Addict
Addict
Posts: 935
Joined: Wed Sep 22, 2010 1:50 am
Location: Bradenton, FL

Re: Unable to read same file in parallel

Post by RichAlgeni »

If you are attempting to read and write the same file, you can get rid of all of the Win API code, and replace it with a mutex: Note the following code is 64 bit.

Writing to a file by overwriting the data. The first line must be executed prior to thread creation.

Code: Select all

Global writeLogLock.i = CreateMutex()

Code: Select all

LockMutex(writeLogLock)

logFile = OpenFile(#PB_Any, "logFile.log")

FileSeek(logFile, 0)
TruncateFile(logFile)
WriteStringN(logFile, FormatDate("%yyyy/%mm/%dd %hh:%ii:%ss", Date()) + " " + logText)

FlushFileBuffers(logFile)
CloseFile(logFile)

UnlockMutex(writeLogLock)
Reading the same file from a different thread:

Code: Select all

LockMutex(writeLogLock)

logFile = ReadFile(#PB_Any, "logFile.log")
fileLength = Lof(logFile)

*datLocation = AllocateMemory(fileLength)
*memLocation = *datLocation

totNeed = fileLength
While totNeed > 0
    amtRead = ReadData(logFile, *memLocation, totNeed)
    If  amtRead > 0
        *memLocation = *memLocation + amtRead
        totNeed      = totNeed      - amtRead
    Else
        Break
    EndIf
Wend

CloseFile(logFile)

UnlockMutex(writeLogLock)

FreeMemory(*datLocation)
If you need to read and write to multiple different files, I've come up with a way that seems to be faster than locking each individual file. I create, insert the file name into the map, if it isn't already there. The process uses a Mutex to update the map.

Code: Select all

Global NewMap readFileList.s()
Global readFileLock.i = CreateMutex()

Code: Select all

; lock the map to add our file, so that we do not have file contention

Repeat
    LockMutex(readFileLock)
    If FindMapElement(readFileList(), fileName) > 0
        UnlockMutex(readFileLock)
        Delay(20)
    Else
        AddMapElement(readFileList(), fileName)
        UnlockMutex(readFileLock)
        Break
    EndIf
ForEver
read or write to your file here

Code: Select all

; lock the map again, so that we can remove file we have read in from the list

LockMutex(readFileLock)
If FindMapElement(readFileList(), fileName) > 0
    DeleteMapElement(readFileList(), fileName)
EndIf
UnlockMutex(readFileLock)
sepp
User
User
Posts: 10
Joined: Sat Jul 28, 2012 11:31 pm

Re: Unable to read same file in parallel

Post by sepp »

Thanks RichAlgeni for your reply.

The manual says that a mutex only works within one programm (but several threads). The code above was just an example, actually I am having 3 seperate applications (1 writer, 2 readers).

You are closing the filehandle before releasing the mutex, that would be something I could use too. But I think that would make things much more complicated, each reader had to open the file, read from it, close it again and again in a very short period. What I wanted to have is an open filehandle in each reader-application and then read from it line by line and only close the filehandle when the application is exited.
User avatar
luis
Addict
Addict
Posts: 3895
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Unable to read same file in parallel

Post by luis »

Code: Select all

#FILE_SHARE_READ | #FILE_SHARE_WRITE
You used logical Or instead of bitwise |
"Have you tried turning it off and on again ?"
sepp
User
User
Posts: 10
Joined: Sat Jul 28, 2012 11:31 pm

Re: Unable to read same file in parallel

Post by sepp »

luis wrote:

Code: Select all

#FILE_SHARE_READ | #FILE_SHARE_WRITE
You used logical Or instead of bitwise |
BINGO :lol:

As #FILE_SHARE_READ=1 and #FILE_SHARE_WRITE=2 the conjunction #FILE_SHARE_READ Or #FILE_SHARE_WRITE = 1 Or 2 = 1 but #FILE_SHARE_READ | #FILE_SHARE_WRITE = 1 | 2 = 3. So it could not work as the Writer-Application needed #FILE_SHARE_WRITE.

Thank you very much!
User avatar
luis
Addict
Addict
Posts: 3895
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Unable to read same file in parallel

Post by luis »

It was my pleasure to help you. You stated your problem clearly and you even wrote a nice little program to show your problem.
Even put the link for the needed include.

Welcome to the forum.
"Have you tried turning it off and on again ?"
User avatar
RichAlgeni
Addict
Addict
Posts: 935
Joined: Wed Sep 22, 2010 1:50 am
Location: Bradenton, FL

Re: Unable to read same file in parallel

Post by RichAlgeni »

You're welcome Sepp!

Luis is not only good, he's dang good!
User avatar
RichAlgeni
Addict
Addict
Posts: 935
Joined: Wed Sep 22, 2010 1:50 am
Location: Bradenton, FL

Re: Unable to read same file in parallel

Post by RichAlgeni »

As is most everyone here! :mrgreen:
Post Reply