is OPEN, Get and Put # (File I/O) thread safe?

New to FreeBASIC? Post your questions here.
Post Reply
SamL
Posts: 58
Joined: Nov 23, 2019 17:30
Location: Minnesota

is OPEN, Get and Put # (File I/O) thread safe?

Post by SamL »

I open different files using a custom file number function that's mutexed.

I am opening the same files sometimes from different threads but all those files are always mutexed. but when opening different files in different threads im thinking there might be an issue with the OPEN / GET / PUT commands.

I'm using put / get and opening the file for random access. Do i need to be using mutexes with OPEN and GET and PUT# (File I/O) regardless of different files using those commands ??

I'm trying to hunt a down bug in my code and this came across my mind as a possible reason for a very very rare crash i need to find.

Thanks every one!
-SamL
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: is OPEN, Get and Put # (File I/O) thread safe?

Post by fxm »

I cannot answer you categorically on the thread safe of such each keyword, but I advise you to use one common mutex for the full code block of access to files in any thread (including the main thread), if there is a possibility of conflict.

Example from the documentation so modified:

Code: Select all

Dim As Byte Ptr lpBuffer
Dim As Integer hFile, Counter, Size

Size = 256

lpBuffer = Allocate(Size)
For Counter = 0 To Size-1
  lpBuffer[Counter] = (Counter And &HFF)
Next

Mutexlock(pMutex)  '' <===

' Get free file file number
hFile = FreeFile()

' Open the file "test.bin" in binary writing mode
Open "test.bin" For Binary Access Write As #hFile

  ' Write 256 bytes from the memory pointed to by lpBuffer
  Put #hFile, , lpBuffer[0], Size

' Close the file
Close #hFile

Mutexunlock(pMutex)  '' <===

' Free the allocated memory
Deallocate lpBuffer
SamL
Posts: 58
Joined: Nov 23, 2019 17:30
Location: Minnesota

Re: is OPEN, Get and Put # (File I/O) thread safe?

Post by SamL »

ok, thanks for the help!

My testing of multi-thread with OPEN, GET and PUT resulted in no issues with multi-threading.

The very very rare crash I was hunting down turned out to be Windows 10 resetting the computer for updates and this caused some files to have unexpected data written to them.

Apparently Windows 10 home has no way to disable updates from restarting the computer with out applying unreliable hacks. So ill have to get windows 10 professional to fix this 'bug'.

Thanks again!
-SamL
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: is OPEN, Get and Put # (File I/O) thread safe?

Post by fxm »

For my personal information, did you put any mutex somewhere in relation to Freefile, Open / Close, Put / Get?
SamL
Posts: 58
Joined: Nov 23, 2019 17:30
Location: Minnesota

Re: is OPEN, Get and Put # (File I/O) thread safe?

Post by SamL »

I only mutexlock for the file number , and Im not using freefile()
Here is the code I used to test. ( I updated the code as I found a few mistakes.. )

NOTE: there is updated code for this test below that uses random strings for testing that will detect issues faster/better than this test.

Code: Select all

'press ESC key to stop test

dim shared as string file_name
file_name = "D:\test\"

Type Check_file_Entry ' used for check file in array_function_dba_checks.bi
    sdata As String * 50
End Type

dim shared as ubyte freefile_numbers(1 to 255)
dim shared as string*100 freefile_owner(1 to 255)
for i as long = 1 to 255
    freefile_owner(i) = "NONE"
next i

namespace mutex
dim as any ptr file_number
end namespace
mutex.file_number = mutexcreate

dim shared as byte Threads_go_now = 0
dim shared as byte threads_can_run = 1

function file_number( parameter as integer = 0 , ff_owner as string = "NONE" ) as integer
    MutexLock mutex.file_number
    dim as integer return_ff = 0
    if parameter = 0 then 'get a number
        for i as integer = lbound(freefile_numbers) to ubound(freefile_numbers)
            if freefile_numbers(i) = 0 then
                freefile_numbers(i) = 1
                freefile_owner(i) = ff_owner
                return_ff = i
                exit for
            end if
        next i
    else 'parameter = some file number, so time to release/reset the freefile number
        freefile_numbers(parameter) = 0
        freefile_owner(parameter) = "NONE"
        return_ff = 0
    end if
    MutexUnLock mutex.file_number
    return return_ff
end function
function new_FF(ff_owner as string) as integer
    dim as integer FF = 0
    while FF = 0
        FF = file_number(,ff_owner)
        if FF = 0 then
            sleep 10,1
        end if
    wend    
    return FF
end function

Sub thread_write( ByVal userdata As Any Ptr )
    
    Dim As Integer id = CInt(userdata)
    print "STARTING THREAD #" & str(id)
    dim checking_entry as Check_file_Entry
    dim as string file = file_name & str(id) & ".txt"
    dim as string file_entry
    dim as integer ff
    
    while Threads_go_now = 0
        sleep id ' this will help allow threads to be un synced and this is what is needed for this test 
    wend
    print "GO " & id
    while THREADS_CAN_RUN = 1    
        ff = new_FF(__function__)
        if open ( file For Random As #ff len=sizeof(Check_file_Entry)) = 0 then    
            for i as integer = 1 to 6
                checking_entry.sdata = "test_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_" & str(id) : put #ff, i, checking_entry
            next i
            close #ff
        else
        end if
        file_number( ff )
        ff = new_FF(__function__)
        if open (file For Random As #ff len=sizeof(Check_file_Entry)) = 0 then    
            for i as integer = 1 to 6
                get #ff, i, checking_entry
                if checking_entry.sdata <>  "test_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_" & str(id) then print "ERROR"
            next i
            close #ff
        else
        end if    
        file_number( ff )
        sleep 1
    wend  
    print "STOP " & id
end sub
    
    dim as any ptr thread_handles(1 to 255)
    for i as integer = 1 to 255
        thread_handles(i) = ThreadCreate(@thread_write, CPtr(Any Ptr, i))
    next i
    sleep 1000
    Threads_go_now = 1
    while inkey <> chr(27)
        sleep 100
    wend
    print "Ending.."
    threads_can_run = 0
    For i As Integer = 1 To 255

        If thread_handles(i) <> 0 Then
            ThreadWait(thread_handles(i))
        End If
    Next

mutexdestroy mutex.file_number

print "done"
sleep
Last edited by SamL on Jul 17, 2020 17:36, edited 1 time in total.
SamL
Posts: 58
Joined: Nov 23, 2019 17:30
Location: Minnesota

Re: is OPEN, Get and Put # (File I/O) thread safe?

Post by SamL »

I learned that using freefile() only works in one thread at a time unless put a mutexlock around freefile() and OPEN.

freefile() checks how many files are open before returning a value. so there is a window of time that freefile() could return the same number if making multiple requests for a file number like in multi threading.

So that's why I'm not using freefile() for multithreading.

I suppose a way to use freefile() while multi threading is

Code: Select all

MutextLock free_file
ff = freefile
if open(file, #ff ) = 0 then
   get / put #ff 
   close #ff
   MutexUnLock free_file

else
   MutexUnLock free_file

endif
Last edited by SamL on Jul 17, 2020 16:55, edited 4 times in total.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: is OPEN, Get and Put # (File I/O) thread safe?

Post by fxm »

Thanks.

By the way, look at this paragraph (#10) of 'Programmer's Guide' / 'Multi-Threading' / 'Critical Sections FAQ' I wrote:
10. Is it better to take precautions when using the keyword 'Sleep' in threads?
  • I was thinking in particular of the 2 Sleep in Sub thread_write():
    • sleep id, 1
      and
      sleep 1, 1
      to try against your crash ?
Last edited by fxm on Jul 17, 2020 16:55, edited 2 times in total.
SamL
Posts: 58
Joined: Nov 23, 2019 17:30
Location: Minnesota

Re: is OPEN, Get and Put # (File I/O) thread safe?

Post by SamL »

ya i did not use sleep x,1 in this test.

I learned this recently that I needed to set all sleeps in threads to sleep x,1 when I started using fbgfx and screenevent while multi threading!

Thanks for the link that clarifies what I was reading in the forums concerning the needed sleep in threads.
I'm saving a link and will be reading the guide.
SamL
Posts: 58
Joined: Nov 23, 2019 17:30
Location: Minnesota

Re: is OPEN, Get and Put # (File I/O) thread safe?

Post by SamL »

I edited the test code so it generates random strings for each thread so it should make the test better.

Code: Select all

'press ESC key to stop test

dim shared as string file_name
file_name = "D:\test\"

Function rnd_range (first As Double, last As Double) As Double
    Function = Rnd * (last - first) + first
End Function

Type Check_file_Entry ' used for check file in array_function_dba_checks.bi
    sdata As String * 50
End Type

dim shared as ubyte freefile_numbers(1 to 255)
dim shared as string*100 freefile_owner(1 to 255)
for i as long = 1 to 255
    freefile_owner(i) = "NONE"
next i

namespace mutex
dim as any ptr file_number
end namespace
mutex.file_number = mutexcreate

dim shared as byte Threads_go_now = 0
dim shared as byte threads_can_run = 1

function file_number( parameter as integer = 0 , ff_owner as string = "NONE" ) as integer
    MutexLock mutex.file_number
    dim as integer return_ff = 0
    if parameter = 0 then 'get a number
        for i as integer = lbound(freefile_numbers) to ubound(freefile_numbers)
            if freefile_numbers(i) = 0 then
                freefile_numbers(i) = 1
                freefile_owner(i) = ff_owner
                return_ff = i
                exit for
            end if
        next i
    else 'parameter = some file number, so time to release/reset the freefile number
        freefile_numbers(parameter) = 0
        freefile_owner(parameter) = "NONE"
        return_ff = 0
    end if
    MutexUnLock mutex.file_number
    return return_ff
end function
function new_FF(ff_owner as string) as integer
    dim as integer FF = 0
    while FF = 0
        FF = file_number(,ff_owner)
        if FF = 0 then
            sleep 10,1
        end if
    wend    
    return FF
end function

Sub thread_write( ByVal userdata As Any Ptr )
    
    Dim As Integer id = CInt(userdata)
    print "STARTING THREAD #" & str(id)
    dim checking_entry as Check_file_Entry
    dim as string file = file_name & str(id) & ".txt"
    dim as integer ff
    dim as string file_code 
    
    for i as integer = 1 to 47
        randomize
        file_code += chr(rnd_range(33,126))
    next i
    
    while Threads_go_now = 0
        sleep id,1 ' this will help allow threads to be un synced and this is what is needed for this test 
    wend
    
    print "GO " & id
    while THREADS_CAN_RUN = 1    
        ff = new_FF(__function__)
        if open ( file For Random As #ff len=sizeof(Check_file_Entry)) = 0 then    
            for i as integer = 1 to 6
                checking_entry.sdata = file_code & str(id) : put #ff, i, checking_entry
            next i
            close #ff
        else
        end if
        file_number( ff )
        ff = new_FF(__function__)
        if open (file For Random As #ff len=sizeof(Check_file_Entry)) = 0 then    
            for i as integer = 1 to 6
                get #ff, i, checking_entry
                if checking_entry.sdata <> file_code & str(id) then print "ERROR"
            next i
            close #ff
        else
        end if    
        file_number( ff )
        sleep 1,1
    wend  
    print "STOP " & id
end sub
    
    dim as any ptr thread_handles(1 to 255)
    for i as integer = 1 to 255
        thread_handles(i) = ThreadCreate(@thread_write, CPtr(Any Ptr, i))
    next i
    sleep 1000
    Threads_go_now = 1
    while inkey <> chr(27)
        sleep 100
    wend
    print "Ending.."
    threads_can_run = 0
    For i As Integer = 1 To 255

        If thread_handles(i) <> 0 Then
            ThreadWait(thread_handles(i))
        End If
    Next

mutexdestroy mutex.file_number

print "done"
sleep
-SamL
Post Reply