Menu

#929 FREEFILE/OPEN race for an unused file number

open
nobody
other
2020-12-30
2020-12-29
TeeEmCee
No

BASIC design flaw. freefile returns an unused file number which is not reserved, so if you open files on multiple threads another thread might intercede, call freefile+openand thus the open on the other thread will fail, returning fberrILLEGAL_CALL.

FreeFile on the wiki mentions "it is wise to use Freefile immediately before its corresponding Open, to ensure that the file number is not returned and opened elsewhere first." But ensure here is false, it doesn't ensure it and if you want to avoid it you need either 1) do your own locking, 2) do your own allocation of file numbers, either statically or reimplementing or wrapping FreeFile, 3) call open in a loop until it succeeds (but how do you distinguish from other errors?)

It would be nice if FreeFile were optional, Open took the file number byref and picked and returned it itself if you pass 0 (which is not a valid file number), which is both a simplification and a fix. (I have my own openfile that does this and happily deleted hundreds of freefile calls.) This would either require an ABI change or an unpleasantly large number of new entry points... but 1.08 changes the ABI anyway.

Another solution might be to keep track of file numbers that have returned from FreeFile but not passed to Open yet, and make FreeFile not return one of these unless the available file numbers would otherwise be exhausted (so programs that don't have matched FreeFile/Open calls don't break). However this changes documented behaviour and can potentially break programs which use a mix of manual file number allocation and FreeFile.

Testcase:

dim shared as integer fh1, fh2

sub tryopen(arg as any ptr)
        dim byref fh as integer = *cast(integer ptr, arg)
        do
                fh = freefile
                if open("dummy.tmp" for binary as fh) then
                        ? "open failed: fh1=" & fh1 & " fh2=" & fh2
                        system
                end if
                ? fh;
                close #fh
        loop
end sub

threadcreate @tryopen, @fh1
tryopen @fh2

Running it always soon ends with "open failed: fh1=1 fh2=1".

Discussion

  • fxm (freebasic.net)

    Another more rational solution, to resolve this conflict between threads and allocation of file numbers, is to define a mutual exclusion using a mutex blocking around the FreeFile...Open sequence:

    dim shared as integer fh1, fh2
    dim as any ptr pt
    dim shared as any ptr mut
    
    sub tryopen(byval arg as any ptr)
            dim byref fh as integer = *cast(integer ptr, arg)
            do
                    mutexlock(mut)
                    fh = freefile
                    if open("dummy.tmp" for binary as fh) then
                            mutexunlock(mut)
                            ? "open failed: fh1=" & fh1 & " fh2=" & fh2
                            mutexdestroy(mut)
                            system
                    end if
                    mutexunlock(mut)
                    ? fh;
                    close #fh
            loop until inkey <> ""
    end sub
    
    mut = mutexcreate()
    pt = threadcreate(@tryopen, @fh1)
    tryopen @fh2
    
    Threadwait(pt)
    mutexdestroy(mut)
    
     
  • TeeEmCee

    TeeEmCee - 2020-12-29

    Yes, I mentioned mutexes as a workaround for this problem. Wrapping every Open call is burdensome though. This isn't an implementation bug, it's a flaw in FB that would ideally be fixed.

     
  • fxm (freebasic.net)

    In first time, I think it is useful to add this constraint in the 'Freefile' documentation page:

    Freefile will always return the smallest free file number. The file number returned by Freefile will not change until that file number is Opened, or until a smaller file number is Closed:
    - For this reason, it is wise to use Freefile immediately before its corresponding Open, to ensure that the same file number is not returned and opened elsewhere first.
    - In case of potential conflict with other threads, this non-breaking 'Freefile...Open' sequence must additionally be considered as a critical section of code and therefore must be protected for example by mutual exclusion (using a mutex locking).

     

    Last edit: fxm (freebasic.net) 2021-01-09

Log in to post a comment.