Sur un système d'exploitation basé sur Debian (Ubuntu, Debian Squeeze), j'utilise Python (2.7, 3.2) FCNTL pour verrouiller un fichier. Si je comprends à partir de ce que j'ai lu, fnctl.flock verrouille Un fichier d'une manière, une exception sera lancée si un autre client souhaite verrouiller le même fichier.
J'ai construit un petit exemple, que je m'attendrais à lancer une exceptaton, car je verrouille d'abord le fichier, puis, immédiatement après, j'essaie de le verrouiller à nouveau:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import fcntl
fcntl.flock(open('/tmp/locktest', 'r'), fcntl.LOCK_EX)
try:
fcntl.flock(open('/tmp/locktest', 'r'), fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
print("can't immediately write-lock the file ($!), blocking ...")
else:
print("No error")
Mais l'exemple imprime simplement "aucune erreur".
Si je divisions ce code jusqu'à deux clients fonctionnant en même temps (un verrouillage puis en attente, l'autre tentative de verrouiller après que le premier verrou est déjà actif), je reçois le même comportement - aucun effet du tout.
Quelle est l'explication de ce comportement?
ÉDITER :
Modifications demandées par NightCracker, cette version imprime également "Aucune erreur", même si je ne m'attendais pas à ce que:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import fcntl
import time
fcntl.flock(open('/tmp/locktest', 'w'), fcntl.LOCK_EX | fcntl.LOCK_NB)
try:
fcntl.flock(open('/tmp/locktest', 'w'), fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
print("can't immediately write-lock the file ($!), blocking ...")
else:
print("No error")
J'ai compris. L'erreur dans mon script est que je crée un nouveau descripteur de fichier sur chaque appel:
fcntl.flock(open('/tmp/locktest', 'r'), fcntl.LOCK_EX | fcntl.LOCK_NB)
(...)
fcntl.flock(open('/tmp/locktest', 'r'), fcntl.LOCK_EX | fcntl.LOCK_NB)
Au lieu de cela, je dois affecter l'objet de fichier à une variable et essayer de verrouiller:
f = open('/tmp/locktest', 'r')
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
(...)
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
Que je reçois aussi l'exception que je voulais voir: IOError: [Errno 11] Resource temporarily unavailable
. Maintenant, je dois penser dans quels cas il est logique d'utiliser FCNTL.
Ancien poste, mais si quelqu'un d'autre le trouve, je reçois ce comportement:
>>> fcntl.flock(open('test.flock', 'w'), fcntl.LOCK_EX)
>>> fcntl.flock(open('test.flock', 'w'), fcntl.LOCK_EX | fcntl.LOCK_NB)
# That didn't throw an exception
>>> f = open('test.flock', 'w')
>>> fcntl.flock(f, fcntl.LOCK_EX)
>>> fcntl.flock(open('test.flock', 'w'), fcntl.LOCK_EX | fcntl.LOCK_NB)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IOError: [Errno 35] Resource temporarily unavailable
>>> f.close()
>>> fcntl.flock(open('test.flock', 'w'), fcntl.LOCK_EX | fcntl.LOCK_NB)
# No exception
On dirait dans le premier cas, le fichier est fermé après la première ligne, probablement parce que l'objet de fichier est inaccessible. La fermeture du fichier libère la serrure.
Je déteste le même problème ... J'ai résolu le fichier ouvert dans une variable séparée:
Ne fonctionnera pas:
fcntl.lockf(open('/tmp/locktest', 'w'), fcntl.LOCK_EX | fcntl.LOCK_NB)
Travaux:
lockfile = open('/tmp/locktest', 'w')
fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
Je pense que le premier ne fonctionne pas parce que le fichier ouvert est déchets collectés , fermé et le verrouillage libéré .
vous pouvez vous référer à cela POST pour plus de détails sur différents schémas de verrouillage.
Quant à votre deuxième question, utilisez fcntl
_ pour obtenir la serrure sur différents processus (utilisez lockf
plutôt que pour la simplicité). Sur Linux lockf
est juste une enveloppe pour fcntl
, les deux sont associés à (pid, inode)
paire.
1. utilisation fcntl.fcntl
Pour fournir un verrouillage de fichier entre les processus.
import os
import sys
import time
import fcntl
import struct
fd = open('/etc/mtab', 'r')
ppid = os.getpid()
print('parent pid: %d' % ppid)
lockdata = struct.pack('hhllh', fcntl.F_RDLCK, 0, 0, 0, ppid)
res = fcntl.fcntl(fd.fileno(), fcntl.F_SETLK, lockdata)
print('put read lock in parent process: %s' % str(struct.unpack('hhllh', res)))
if os.fork():
os.wait()
lockdata = struct.pack('hhllh', fcntl.F_UNLCK, 0, 0, 0, ppid)
res = fcntl.fcntl(fd.fileno(), fcntl.F_SETLK, lockdata)
print('release lock: %s' % str(struct.unpack('hhllh', res)))
else:
cpid = os.getpid()
print('child pid: %d' % cpid)
lockdata = struct.pack('hhllh', fcntl.F_WRLCK, 0, 0, 0, cpid)
try:
fcntl.fcntl(fd.fileno(), fcntl.F_SETLK, lockdata)
except OSError:
res = fcntl.fcntl(fd.fileno(), fcntl.F_GETLK, lockdata)
print('fail to get lock: %s' % str(struct.unpack('hhllh', res)))
else:
print('succeeded in getting lock')
2. Utilisez fcntl.lockf
.
import os
import time
import fcntl
fd = open('/etc/mtab', 'w')
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
if os.fork():
os.wait()
fcntl.lockf(fd, fcntl.LOCK_UN)
else:
try:
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError as e:
print('failed to get lock')
else:
print('succeeded in getting lock')
Vous devez passer dans le descripteur de fichier (disponible en appelant la méthode Fileno () de l'objet Fichier). Le code ci-dessous jette un ioerror lorsque le même code est exécuté dans un interprète séparé.
>>> import fcntl
>>> thefile = open('/tmp/testfile')
>>> fd = thefile.fileno()
>>> fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
Essayer:
global f
f = open('/tmp/locktest', 'r')
Lorsque le fichier est fermé, le verrou disparaîtra.