Skip to content
This repository was archived by the owner on Oct 10, 2020. It is now read-only.
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 34 additions & 30 deletions Atomic/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
ATOMIC_CONFD = os.environ.get('ATOMIC_CONFD', '/etc/atomic.d/')
ATOMIC_LIBEXEC = os.environ.get('ATOMIC_LIBEXEC', '/usr/libexec/atomic')
ATOMIC_VAR_LIB = os.environ.get('ATOMIC_VAR_LIB', '/var/lib/atomic')
if not os.path.exists(ATOMIC_VAR_LIB):
os.makedirs(ATOMIC_VAR_LIB)
ATOMIC_INSTALL_JSON = os.environ.get('ATOMIC_INSTALL_JSON', os.path.join(ATOMIC_VAR_LIB, 'install.json'))

GOMTREE_PATH = "/usr/bin/gomtree"
Expand Down Expand Up @@ -805,32 +803,32 @@ def load_scan_result_file(file_name):
def file_lock(func):
lock_file_name = "{}.lock".format(os.path.join(os.path.dirname(ATOMIC_INSTALL_JSON), "." + os.path.basename(ATOMIC_INSTALL_JSON)))

# Create the temporary lockfile if it doesn't exist
if not os.path.exists(lock_file_name):
open(lock_file_name, 'a').close()

install_data_file = open(lock_file_name, "r")

def get_lock():
'''
Obtains a read-only file lock on the install data
:return:
'''
time_out = 0
f_lock = False
while time_out < 10.5: # Ten second attempt to get a lock
try:
fcntl.flock(install_data_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
f_lock = True
break
except IOError:
time.sleep(.5)
time_out += .5
# Create the temporary lockfile if it doesn't exist
if not os.path.exists(lock_file_name):
open(lock_file_name, 'a').close()

with open(lock_file_name, "r") as f:
while time_out < 10.5: # Ten second attempt to get a lock
try:
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this lock be released when f is closed though?
Just tried this out:

with open("lockfile", "r") as f:
    fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
with open("lockfile", "r") as f:
    fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)

which works without issue. Whereas

f = open("lockfile", "r")
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)

g = open("lockfile", "r")
fcntl.flock(g, fcntl.LOCK_EX | fcntl.LOCK_NB)

throws a BlockingIOError as expected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, let's try making it a context manager. There is also https://pypi.python.org/pypi/flockcontext ...dunno about adding the dep.

f_lock = True
break
except IOError:
time.sleep(.5)
time_out += .5
if not f_lock:
raise ValueError("Unable to get file lock for {}".format(ATOMIC_INSTALL_JSON))

def release_lock():
fcntl.flock(install_data_file, fcntl.LOCK_UN)
with open(lock_file_name, "r") as f:
fcntl.flock(f, fcntl.LOCK_UN)

def wrapper(*args, **kwargs):
get_lock()
Expand All @@ -841,34 +839,40 @@ def wrapper(*args, **kwargs):
return wrapper


# Records additional data for containers outside of the native storage (docker/ostree)
class InstallData(object):
if not os.path.exists(ATOMIC_INSTALL_JSON):
open(ATOMIC_INSTALL_JSON, 'a').close()

install_file_handle = open(ATOMIC_INSTALL_JSON, 'r')

@staticmethod
def _read_install_data(file_handle):
@classmethod
def _read_install_data(cls):
try:
return json.loads(file_handle.read())
except ValueError:
return {}
with open(ATOMIC_INSTALL_JSON, 'r') as f:
# Backwards compatibilty - we previously created an empty file explicitly;
# see https://github.com/projectatomic/atomic/pull/966
if os.fstat(f.fileno()).st_size == 0:
return {}
return json.load(f)
except IOError as e:
if e.errno == errno.ENOENT:
return {}
raise e

@classmethod
def _write_install_data(cls, new_data):
install_data = cls._read_install_data(cls.install_file_handle)
install_data = cls._read_install_data()
for x in new_data:
install_data[x] = new_data[x]
temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
json.dump(install_data, temp_file)
temp_file.close()
if not os.path.exists(ATOMIC_VAR_LIB):
os.makedirs(ATOMIC_VAR_LIB)
shutil.move(temp_file.name, ATOMIC_INSTALL_JSON)

@classmethod
@file_lock
def read_install_data(cls):
if os.path.exists(ATOMIC_INSTALL_JSON):
read_data = cls._read_install_data(cls.install_file_handle)
read_data = cls._read_install_data()
return read_data
return {}

Expand All @@ -880,7 +884,7 @@ def write_install_data(cls, new_data):
@classmethod
def get_install_name_by_id(cls, iid, install_data=None):
if not install_data:
install_data = cls._read_install_data(cls.install_file_handle)
install_data = cls._read_install_data()
for installed_image in install_data:
if install_data[installed_image]['id'] == iid:
return installed_image
Expand All @@ -889,7 +893,7 @@ def get_install_name_by_id(cls, iid, install_data=None):
@classmethod
@file_lock
def delete_by_id(cls, iid, ignore=False):
install_data = cls._read_install_data(cls.install_file_handle)
install_data = cls._read_install_data()
try:
id_key = InstallData.get_install_name_by_id(iid, install_data=install_data)
except ValueError as e:
Expand Down