Sebuah byte-range lock dapat melindungi inode yang sama tetapi memiliki semantik lifetime yang sangat berbeda bergantung pada objek pemilik lock. Record lock fcntl() tradisional terkait dengan proses. Open file description lock justru menempel pada open file description kernel yang dirujuk oleh descriptor. Pergeseran ini mengubah operasi close mana yang melepas lock, state yang bertahan setelah fork(), serta kemampuan dua thread dalam satu proses untuk saling berkompetisi pada region file yang sama.
Linux menyediakan model ini melalui F_OFD_SETLK, F_OFD_SETLKW, dan F_OFD_GETLK. Model range tetap serupa: struct flock menentukan read lock, write lock, atau unlock beserta offset dan panjangnya. Perbedaan utamanya terletak pada kepemilikan.
Descriptor bukan pemilik lock
File descriptor adalah entri pada descriptor table milik proses. Entri tersebut merujuk ke open file description yang dipelihara kernel. Beberapa descriptor dapat merujuk ke satu open file description setelah operasi seperti dup(), dan child yang dibuat melalui fork() dapat mewarisi descriptor yang merujuk ke description yang sama.
OFD lock dimiliki oleh open file description bersama tersebut, bukan oleh satu nomor descriptor. Menutup satu hasil duplikasi tidak melepas lock selama descriptor lain masih merujuk ke description yang sama. Pelepasan otomatis terjadi ketika referensi terakhir ke open file description tersebut ditutup.
Perilaku ini berbeda secara material dari record lock tradisional yang terkait dengan proses. Pada Linux, menutup descriptor apa pun untuk sebuah file dapat melepas seluruh record lock tradisional yang dimiliki proses pada file tersebut, meskipun descriptor yang ditutup bukan descriptor yang dipakai untuk memperoleh lock. Kode yang menganggap satu descriptor sebagai token lifetime untuk record lock tradisional dapat kehilangan proteksi akibat close yang tidak berkaitan langsung.
OFD lock membuat lifetime lock lebih selaras dengan lifetime instance file kernel yang membawanya.
dup() mempertahankan satu domain kepemilikan
Duplikasi descriptor tidak menciptakan pemilik OFD lock yang independen:
int a = open("state.bin", O_RDWR);
int b = dup(a);a dan b sama-sama merujuk ke open file description yang sama. OFD write lock yang diperoleh melalui a dapat diubah atau dilepas melalui b. Request OFD yang berpotensi konflik melalui salah satu descriptor diperlakukan berasal dari domain kepemilikan yang sama.
Properti ini relevan ketika descriptor melewati beberapa layer program. Wrapper dapat menduplikasi descriptor tanpa sengaja membuat pemilik lock yang saling bersaing. Hasil duplikasi tetap menjadi alias ke objek kernel yang sama.
Pemanggilan open() terpisah memiliki perilaku berbeda:
int a = open("state.bin", O_RDWR);
int b = open("state.bin", O_RDWR);Walaupun kedua descriptor mengarah ke file yang sama, keduanya secara normal merujuk ke open file description yang berbeda. OFD lock yang diperoleh melalui description tersebut dapat saling konflik. Pembeda utamanya bukan identitas pathname, identitas inode saja, atau nomor descriptor. Pembeda tersebut adalah open file description yang dicapai oleh descriptor pada operasi locking.
fork() dapat memperpanjang lifetime lock
Descriptor yang diwariskan melalui fork() tetap merujuk ke open file description yang sama. Child karena itu berbagi kepemilikan OFD lock yang terkait dengan description tersebut. Jika parent menutup descriptornya sementara child masih menyimpan referensi hasil pewarisan, lock dapat tetap aktif.
Perilaku ini berguna ketika proses sengaja menyerahkan resource yang sudah terbuka kepada child. Namun, kondisi tersebut juga dapat membuat lifetime lock lebih panjang dari perkiraan parent.
Urutan berikut menggambarkan batas lifetime tersebut:
parent membuka file
parent memperoleh OFD lock
parent melakukan fork
child menyimpan descriptor warisan
parent menutup descriptornya
lock tetap aktif
child menutup referensi terakhir
lock dapat dilepasKontrak lifecycle karena itu harus memperhitungkan pewarisan descriptor. FD_CLOEXEC mengendalikan apakah descriptor tertentu tetap terbuka melewati execve(), tetapi flag tersebut tidak secara retroaktif mengubah referensi yang sudah diwarisi child sebelum program baru dieksekusi.
Invariant yang relevan tetap sederhana: kepemilikan mengikuti open file description, dan description tetap hidup selama masih ada referensi yang hidup.
Open terpisah memungkinkan contention antar-thread
Record lock tradisional yang terkait dengan proses bukan primitive yang tepat untuk koordinasi thread yang perlu saling berkompetisi pada region file karena kepemilikannya berada pada scope proses. OFD lock dapat merepresentasikan susunan yang berbeda.
Setiap thread dapat menjalankan open() sendiri dan memperoleh open file description yang berbeda. Lock melalui description independen tersebut kemudian dapat saling konflik meskipun caller berada dalam proses yang sama. Dengan demikian, mekanisme locking kernel dapat merepresentasikan contention antar-thread ketika aplikasi secara sengaja memberikan open file description terpisah kepada setiap thread.
Kebalikannya juga penting. Memberikan descriptor hasil duplikasi kepada dua thread tidak menciptakan pemilik lock independen. Jika kedua descriptor merujuk ke open file description yang sama, kernel memperlakukan OFD lock keduanya sebagai bagian dari domain kepemilikan yang sama. Correctness sinkronisasi karena itu bergantung pada asal descriptor, bukan sekadar nilai integer descriptor yang berbeda.
Semantik range tetap mendukung konversi parsial
OFD lock mempertahankan semantik byte-range. struct flock mengidentifikasi range melalui l_whence, l_start, dan l_len. Nilai l_len nol memperpanjang range dari posisi awal hingga akhir file, termasuk pertumbuhan file berikutnya yang tercakup oleh definisi tersebut.
Request lock baru melalui open file description yang sama dapat mengonversi region yang sudah terkunci. Bergantung pada overlap, kernel dapat memecah, memperkecil, atau menggabungkan region lock. Aplikasi karena itu sebaiknya memodelkan state lock sebagai kumpulan range, bukan satu Boolean yang menempel pada file.
Sebagai contoh, mengganti write lock pada byte 0 sampai 4095 dengan read lock pada byte 1024 sampai 2047 hanya mengubah subrange yang diminta. Region di sekelilingnya dapat tetap memiliki write lock dalam domain kepemilikan yang sama.
Konversi range berguna bagi storage engine yang membagi file menjadi region dengan koordinasi independen, tetapi cleanup juga menjadi sensitif terhadap offset yang tepat. Request unlock memengaruhi range yang ditentukan dan tidak secara implisit menghapus setiap OFD lock yang terkait dengan description tersebut.
Status advisory tetap sama
Semantik kepemilikan OFD tidak mengubah advisory locking menjadi mandatory access control. Proses yang bekerja sama dan memakai operasi locking kompatibel dapat berkoordinasi pada range yang dilindungi. Proses yang langsung menjalankan read atau write biasa tidak otomatis diblokir hanya karena ada OFD advisory lock.
Lock tersebut merupakan kontrak sinkronisasi di antara partisipan yang mematuhi protokol. Lock bukan boundary authorization terhadap kode arbitrer yang memiliki akses ke file.
Perbedaan ini juga penting saat menganalisis kegagalan. Data race akibat komponen yang mengabaikan protokol locking bukan bukti bahwa kernel kehilangan OFD lock. Protokolnya sendiri telah dilewati.
Perilaku filesystem dan network filesystem juga perlu divalidasi secara eksplisit untuk target deployment. Aplikasi tidak seharusnya menganggap perilaku locking remote identik dengan filesystem lokal hanya karena interface fcntl() yang digunakan sama.
OFD dan record lock tradisional berbagi ruang konflik
OFD lock bukan mekanisme paralel yang sepenuhnya terisolasi dari lock F_SETLK tradisional. Pada Linux, OFD lock dan record lock tradisional yang tidak kompatibel saling konflik, bahkan ketika proses yang sama mencoba keduanya pada file yang sama.
Interaksi ini relevan saat migrasi. Mengganti hanya sebagian code path dengan operasi OFD dapat menciptakan contention antara jalur lama dan baru. Aturan kepemilikannya berbeda, tetapi byte range keduanya tetap dapat saling memblokir.
F_OFD_GETLK dapat memeriksa lock yang berkonflik. Ketika konflik berasal dari OFD lock, l_pid dilaporkan sebagai -1 karena pemiliknya tidak direpresentasikan oleh process ID. Untuk operasi OFD yang dikirim caller, l_pid harus diatur ke nol.
Tidak adanya PID bukan berarti bookkeeping hilang. Kondisi itu mencerminkan model kepemilikan: satu open file description dapat dirujuk oleh beberapa descriptor dan dapat melintasi batas proses melalui pewarisan.
Blocking acquisition memiliki boundary deadlock berbeda
F_OFD_SETLK melakukan acquisition nonblocking dan gagal ketika lock yang tidak kompatibel mencegah penempatan. F_OFD_SETLKW menunggu hingga lock yang berkonflik tersedia dan dapat mengembalikan EINTR ketika proses penantian diinterupsi oleh signal yang tertangkap.
Ada perbedaan implementasi penting pada deteksi deadlock. Linux melakukan deteksi deadlock untuk blocking record lock tradisional yang terkait dengan proses, tetapi implementasi OFD locking tidak menyediakan deteksi deadlock yang setara. Dua execution path yang memperoleh OFD lock independen dengan urutan berlawanan dapat menunggu tanpa batas jika aplikasi tidak menyediakan timeout, jalur cancellation, atau disiplin ordering global.
Primitive kernel tersebut tidak menggantikan desain urutan lock. Sistem yang memakai beberapa range atau beberapa file tetap memerlukan urutan acquisition deterministik atau mekanisme lain yang membatasi circular wait.
Semantik lifetime menjadi keputusan desain utama
OFD lock paling relevan ketika kepemilikan lock harus mengikuti open file description bersama, bukan proses. Pilihan tersebut membuat descriptor hasil duplikasi bekerja sebagai satu domain, memungkinkan referensi warisan mempertahankan kepemilikan melewati fork(), serta memungkinkan pemanggilan open() independen membentuk pemilik berbeda bahkan di dalam satu proses.
Properti yang sama juga menentukan failure mode. Descriptor warisan yang tidak diperkirakan dapat mempertahankan lock. Dua descriptor yang terlihat independen sebenarnya dapat berbagi satu pemilik setelah duplikasi. Dua descriptor untuk inode yang sama dapat saling berkompetisi jika berasal dari pemanggilan open terpisah. Blocking acquisition dapat masuk ke deadlock yang tidak didiagnosis kernel.
API ini tetap merupakan advisory byte-range locking. Kontrak yang membedakannya terletak pada objek pemilik lock. Ketika kepemilikan melekat pada open file description, topologi descriptor menjadi bagian dari correctness concurrency.