samba: ลองเขียน foxpro ทำการล๊อกไฟล์และเรคคอร์ดใช้เอง

update 50-10-20
ตอนที่ทดลองนี้ cifs บนเดเบียน sid (samba-3.0.26a-1) แก้ปัญหาเรื่องแคชไม่ตรง และล๊อกเรคคอร์ดช้าได้แล้ว
โดยการแก้ไขไฟล์ smb.ini ในหมวด share ให้มีพารามิเตอร์คือ

[data]
    ...
    strict locking = yes
oplocks = yes
level2 oplocks = no
    ...

และเมานต์ด้วยพารามิเตอร์ directio เช่น
$ sudo mount -t cifs //server1/data /mnt/data -o username=USER,password=PASSWORD,iocharset=utf8,directio

จะสามารถล๊อกเรคคอร์ดได้ตรงและแก้ปัญหาแคชไฟล์ได้แล้วครับ

เป็นแค่การทดลองเท่านั้น

ถ้าเครื่องลูกข่ายของ foxpro เป็นวินโดวส์ล้วน ไม่ต้องใช้ฟังก์ชั่นในบล๊อกอันนี้ ซึ่งเป็นการเขียนฟังก์ชั่นเพื่อแก้ปัญหาการล๊อกเรคคอร์ดสำหรับลูกข่ายที่เป็นลินุกซ์เท่านั้น

เนื่องจากการเมานต์แบบ cifs มีปัญหากับแคช ทำให้การอ่านค่าเรคคอร์ดในไฟล์ไม่ตรง จึงต้องเลือกใช้การเมานต์แบบ smbfs แทน ซึ่งการเมานต์แบบ smbfs มีข้อเสียอย่างแรงคือ ไม่สามารถล๊อกไฟล์และเรคคอร์ดได้ จึงทดลองเขียนฟังก์ชั่นการล๊อกไฟล์ขึ้นมาใช้เอง
(ยังค้นคำตอบสำหรับการนี้ในกูเกิลไม่พบ - พบแต่คำถาม ไม่พบคำตอบ ส่วนใหญ่ย้ายไปใช้ระบบฐานข้อมูลกันหมด)

วิธีการคือสร้างไฟล์ข้อมูล dbf ขึ้นมาเก็บค่า ตาราง, เลขที่เรคคอร์ด, เครื่องที่ทำการล๊อก, และเวลาตอนล๊อก
เวลาต้องการล๊อกไฟล์และเรคคอร์ด ก็ให้มาเขียนลงในไฟล์นี้ทุกครั้ง และตอนปลดล๊อกก็ลบออกทุกครั้งเช่นกัน
โดยเวลาเรียกใช้สำหรับการล๊อกไฟล์ ก็เรียกด้วยฟังก์ชั่น =fxflock() และการล๊อกเรคคอร์ดก็เรียกด้วยฟังก์ชั่น =fxrlock()

จากการทดลองพบว่า ถึงแม้แคชของ smbfs จะเร็วกว่า cifs มากก็ตาม แต่ก็ยังไม่ใช่แบบทันทีทันใด
เราจะแก้ปัญหาแคชด้วยการใช้คำสั่ง foxpro ว่า go recno() เพื่ออัปเดตแคช

และพบว่าถ้าเรามีการ เปลี่ยนแปลงข้อมูลในเรคคอร์ด จะทำให้การอัปเดตรวดเร็วขึ้น จึงใช้เทคนิกอันนี้ในการรีเฟรช โดยทุกครั้งที่เกิดการรีเฟรช จะเขียนชื่อเครื่อง (gcMachineName) ลงไปที่เรคคอร์ดแรกของไฟล์ lockhndl

ถึงแม้จะแก้ปัญหาได้ดีพอควรก็ตาม แต่ยังไม่พอสำหรับการล๊อกไฟล์ ซึ่งเราต้องปล่อยให้เร็วที่สุด
ดังนั้นสำหรับการล๊อกไฟล์ จะกันเหนียวอีกชั้นนึงด้วยการเขียนลงไฟล์สำหรับการล๊อค (ข้อมูลว่าง ๆ ) มาช่วยอีกแรงนึง จึงสามารถล๊อกและปล่อยล๊อกได้แบบทันทีทันใด

เนื่องจากมีการใช้ข้อมูลในการล๊อกเป็นชื่อเครื่องด้วย ซึ่งปรกติเราเรียกผ่านฟังก์ชั่น foxpro คือ sys(0) แต่พบว่าเครื่องลูกข่ายที่เป็นลินุกซ์ (+dosemu+freedos) ไม่มีข้อมูลตัวนี้ เราจึงต้องกำหนดข้อมูลตัวนี้ขึ้นมาเองในทุก ๆ เครื่อง ซึ่งในโค๊ดจะปรากฎในชื่อตัวแปร gcMachineName

ส่วนโค๊ด

function fJustFName && RETURN JUST FILENAME
parameter cFile
    private cLoc,cSep
    cLoc=locfile(cFile)
    cSep=iif('/'$cLoc,'/','\')
return right(cLoc,len(cLoc)-rat(cSep,cLoc))

function fJustPath  && RETURN JUST FILENAME
parameter cFile,lIsIncludeSep
    private cLoc,cSep
    cLoc=locfile(cFile)
    cSep=iif('/'$cLoc,'/','\')
    if lIsIncludeSep
        return left(cLoc,rat(cSep,cLoc))
    else
        return left(cLoc,rat(cSep,cLoc)-1)
    endif
return

function fJustStem  && RETURN STEM OF FILENAME
parameter cFile
    private cName
    cName=fJustFName(cFile)
return left(cName,rat('.',cName)-1)

function fxflock    && RETURN FLOCK STATUS
*   SOLVED LOCKED FAIL IN LINUX
*   BY CREATE NEW LOCKED TABLE TO HANDLE LOCKED STATE
*       FLOCK([<expN> | <expC>])
parameter xArea
return fxSubLock(xArea, 0)

function fxrlock    && RETURN RLOCK STATUS
*   SOLVED LOCKED FAIL IN LINUX
*   BY CREATE NEW LOCKED TABLE TO HANDLE LOCKED STATE
*       RLOCK([<expN> | <expC1>]
parameter xArea
return fxSubLock(xArea, -1)

function fxFlushLock
parameter cFile
    use (cFile)
    go 1    && RESERVE FIRST RECORD FOR FLUSH DATA
    replace ftable with gcMachineName
    go bott
    flush
    go 1
    list nocon
    use
    flush
return

function fxSubLock
parameter xArea, nRecno
    private cTable,nLoop
    nLoop=50        && TRY TO LOCK FOR 50 TIMES
    cLockHndl='lockhndl'
    =fxCrLockHndl()
    nOldArea=sele()
    xTemp=type('xArea')
    if xTemp='N' .or. xTemp='C'
        sele (xArea)
    else
        if len(dbf())=0
            return .F.
        endif
    endif
    if nRecno=-1
        nRecno=recno()
    endif
    cTable=fjuststem(dbf())
    *****
    if nRecno=0
        do while file(cTable+'.LCK') .and. nLoop>0
            wait wind "Wait file lock "+cTable timeout .1
        enddo
        if nLoop=0
            return .F.
        endif
        nloop = 50
    endif
    if nRecno=0
        cSafe = set('safe')
        set safe off
        disp to file (cTable+'.LCK') noconsole
        set safe &cSafe
    endif
    *****
    sele 0
    =fxFlushLock(cLockHndl)
    use (cLockHndl)
    locate for fTable=cTable .and. (fRecno=nRecno .or. fRecno=0) .and. fMachine!=gcMachinename
    do while found() .and. nLoop > 0
        *wait wind "Wait for unlock..." timeout 1
        wait wind str(nloop,3)+" wait "+ftable+str(frecno,4) timeout .1
        *wait wind "Wait for unlock..." nowait
        go recno()
        go bott
        go top
        locate for fTable=cTable .and. (fRecno=nRecno .or. fRecno=0) .and. fMachine!=gcMachinename
        nLoop=nLoop-1
    enddo
    if nLoop<=0
        wait wind "Lock failed" nowait
        use
        =fxFlushLock(cLockHndl)
        sele (nOldArea)
        return .F.
    endif
    wait wind "pl" nowait   && PASS LOCK
    locate for fTable=cTable .and. fMachine=gcMachineName
    if ! found()
        locate for fTable=" "
        if ! found()
            appe blank
        endif
        repl fTable with cTable, fMachine with gcMachineName
    endif
    repl fRecno with nRecno, fSeconds with int(seconds())
    *CLEAR UNFINISH WORK
    =fxClearLongLock()
    use
    =fxFlushLock(cLockHndl)
    sele (nOldArea)
return .T.

function fxClearLongLock
    nSeconds=seconds()
    repl fTable with " ", fMachine with " ";
        fRecno with 0, fSeconds with 0;
    for fMachine=gcMachineName .and. abs(fSeconds-nSeconds)>120;
        .and. fSeconds!=0
return .T.

function fxCrLockHndl
    cLockHndl='lockhndl'
    if ! file(cLockHndl+'.dbf')
        create table (cLockHndl) (;
            fTable  c (20);
            ,fRecno n (10);
            ,fMachine   c (20);
            ,fSeconds   n (10)  )
    endif
return .T.

function fxUnlock
*   SOLVED LOCKED FAIL IN LINUX
*   BY CREATE NEW LOCKED TABLE TO HANDLE LOCKED STATE
*       UNLOCK [IN <expN> | <expC> | '*ALL']
parameter xArea
    cLockHndl='lockhndl'
    =fxCrLockHndl()
    nOldArea=sele()
    xTemp=type('xArea')
    if xTemp='C' .and. upper(xArea)='*ALL'
        sele 0
        =fxFlushLock(cLockHndl)
        use lockhndl
        repl fTable with ' ', fRecno with 0;
            , fMachine with ' ', fSeconds with 0;
            for fMachine=gcMachineName
        *CLEAR UNFINISH WORK
        =fxClearLongLock()
        use
        =fxFlushLock(cLockHndl)
        sele (nOldArea)
        return .T.
    endif
    if xTemp='N' .or. xTemp='C'
        sele (xArea)
    else
        if len(dbf())=0
            return .F.
        endif
    endif
    cTable=fjuststem(dbf())
    if file(cTable+'.LCK')
        dele file (cTable+'.LCK')
    endif
    sele 0
    =fxFlushLock(cLockHndl)
    use (cLockHndl)
    repl fTable with ' ', fRecno with 0;
        , fMachine with ' ', fSeconds with 0;
        for fMachine=gcMachineName .and. fTable=cTable
    *CLEAR UNFINISH WORK
    =fxClearLongLock()
    use
    =fxFlushLock(cLockHndl)
    sele (nOldArea)
return .T.

เรียกใช้งานด้วย

=fxflock() && IN CURRENT AREA
...DO UPDATE...
=fxunlock()

และ

=fxrlock() && IN CURRENT AREA AND CURRENT RECORD
...DO UPDATE...
=fxunlock()

ลองใช้งานแล้วได้ผลดีพอควร แต่ยังขาดความสมบูรณ์อีกมาก ถ้าจะนำไปใช้จริงควรปรับแก้เอาเองครับ

Topic: