~xubuntu-dev/ubuntu-cdimage/xubuntu-base

« back to all changes in this revision

Viewing changes to lib/cdimage/multipidfile.py

  • Committer: Sean Davis
  • Date: 2018-03-28 10:32:07 UTC
  • mfrom: (1559.1.156 ubuntu-cdimage)
  • Revision ID: smd.seandavis@gmail.com-20180328103207-o6s9d6h0hxxh8eqc
Merge lp:ubuntu-cdimage rev 1715

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2013 Canonical Ltd.
 
1
# Copyright (C) 2013, 2016 Canonical Ltd.
2
2
# Author: Colin Watson <cjwatson@ubuntu.com>
3
3
 
4
4
# This program is free software: you can redistribute it and/or modify
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
 
"""Atomic counting semaphore files."""
 
16
"""PID files containing multiple PIDs."""
17
17
 
18
18
from __future__ import print_function
19
19
 
27
27
__metaclass__ = type
28
28
 
29
29
 
30
 
class SemaphoreError(Exception):
 
30
class MultiPIDFileError(Exception):
31
31
    pass
32
32
 
33
33
 
34
 
class Semaphore:
35
 
    """A shared lock which only opens when all users have unlocked."""
 
34
class MultiPIDFile:
 
35
    """A file tracking multiple PIDs."""
36
36
 
37
37
    def __init__(self, path):
38
38
        self.path = path
39
39
        self.lock_path = "%s.lock" % path
40
40
 
41
41
    def __str__(self):
42
 
        return "semaphore %s" % self.path
 
42
        return "multipidfile %s" % self.path
43
43
 
44
44
    def __enter__(self):
45
45
        command = ["lockfile", "-r", "4", self.lock_path]
46
46
        if subprocess.call(command) != 0:
47
 
            raise SemaphoreError("Cannot acquire lock on %s!" % self)
 
47
            raise MultiPIDFileError("Cannot acquire lock on %s!" % self)
48
48
 
49
49
    def __exit__(self, unused_exc_type, unused_exc_value, unused_exc_tb):
50
50
        osextras.unlink_force(self.lock_path)
55
55
            "Called _read on %s without locking!" % self)
56
56
        try:
57
57
            with open(self.path) as fd:
58
 
                return int(fd.read())
 
58
                pids = set(int(line) for line in fd)
 
59
                return set(pid for pid in pids if osextras.pid_exists(pid))
59
60
        except IOError as e:
60
61
            if e.errno == errno.ENOENT:
61
 
                return 0
 
62
                return set()
62
63
            raise
63
64
 
64
 
    def _add(self, offset):
 
65
    def _write(self, pids):
65
66
        # Must be called within context manager lock.
66
67
        assert os.path.exists(self.lock_path), (
67
 
            "Called _add on %s without locking!" % self)
68
 
        cur = self._read()
69
 
        with open(self.path, "w") as fd:
70
 
            print(cur + offset, file=fd)
71
 
        return cur + offset
 
68
            "Called _write on %s without locking!" % self)
 
69
        if pids:
 
70
            with open(self.path, "w") as fd:
 
71
                for pid in sorted(pids):
 
72
                    print(pid, file=fd)
 
73
        else:
 
74
            osextras.unlink_force(self.path)
72
75
 
73
76
    @property
74
77
    def state(self):
75
 
        """Return current state of semaphore."""
 
78
        """Return current set of tracked PIDs."""
76
79
        with self:
77
80
            return self._read()
78
81
 
79
 
    def test_increment(self):
80
 
        """Test, increment, return state of test."""
81
 
        with self:
82
 
            state = self._read()
83
 
            self._add(1)
84
 
            return state
85
 
 
86
 
    def decrement_test(self):
87
 
        """Decrement, test, return state of test.
88
 
 
89
 
        It is an error to call decrement-test on a semaphore that is already
90
 
        zero.
91
 
        """
92
 
        with self:
93
 
            state = self._read()
94
 
            if state == 0:
95
 
                osextras.unlink_force(self.path)
96
 
                raise SemaphoreError(
97
 
                    "Attempted to decrement %s when already zero!" % self)
98
 
            state = self._add(-1)
99
 
            if state == 0:
100
 
                osextras.unlink_force(self.path)
101
 
            return state
 
82
    def test_add(self, pid):
 
83
        """Test, add PID, return state of test.
 
84
 
 
85
        It is an error to add a PID that is already present.
 
86
        """
 
87
        with self:
 
88
            pids = self._read()
 
89
            if pid in pids:
 
90
                raise MultiPIDFileError(
 
91
                    "Attempted to add PID %d to %s which was already "
 
92
                    "present!" % (pid, self))
 
93
            state = set(pids)
 
94
            pids.add(pid)
 
95
            self._write(pids)
 
96
            return state
 
97
 
 
98
    def remove_test(self, pid):
 
99
        """Remove PID, test, return state of test.
 
100
 
 
101
        It is an error to remove a PID that is not already present.
 
102
        """
 
103
        with self:
 
104
            pids = self._read()
 
105
            try:
 
106
                pids.remove(pid)
 
107
            except KeyError:
 
108
                raise MultiPIDFileError(
 
109
                    "Attempted to remove PID %d from %s which was not "
 
110
                    "present!" % (pid, self))
 
111
            self._write(pids)
 
112
            return pids
102
113
 
103
114
    @contextlib.contextmanager
104
 
    def held(self):
 
115
    def held(self, pid):
105
116
        try:
106
 
            yield self.test_increment()
 
117
            yield self.test_add(pid)
107
118
        finally:
108
 
            self.decrement_test()
 
119
            self.remove_test(pid)