1#
2# Copyright (C) 2018 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the 'License');
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an 'AS IS' BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import time
18
19from host_controller import common
20from host_controller.utils.ipc import file_lock
21
22MIN_SLEEP_TIME_IN_SECS = 1
23MAX_SLEEP_TIME_IN_SECS = 60
24
25
26class FileLockSemaphore(file_lock.FileLock):
27    """Class for system-wide semaphores for inter process-group sync.
28
29    Inherited FileLock to make the decrement and increment operation atomic,
30    thus any read and write operations to the file must be leaded by Lock()
31    and be followed by Unlock() operations, defined in FileLock
32
33    Attributes:
34        _name: string, name of the file used as semaphore.
35    """
36
37    def __init__(self, name):
38        super(FileLockSemaphore, self).__init__(name, "r+")
39        self._name = name
40        self._InitSemaphore()
41
42    def Acquire(self):
43        """P() operation for the FileLockSemaphore.
44
45        To minimize the starvation, the sleep time will decrease
46        for the processes that have waited longer than the others.
47        """
48        sleep_time = MAX_SLEEP_TIME_IN_SECS
49        while self.LockDevice(self._name, True, True):
50            if self.sem_value > 0:
51                break
52            self.UnlockDevice(self._name)
53            time.sleep(sleep_time)
54            sleep_time = max(sleep_time / 2, MIN_SLEEP_TIME_IN_SECS)
55
56        self._Decrement()
57        self.UnlockDevice(self._name)
58
59    def Release(self):
60        """V() operation for the FileLockSemaphore."""
61        self.LockDevice(self._name, True, True)
62        self._Increment()
63        self.UnlockDevice(self._name)
64
65    def _InitSemaphore(self):
66        """Initializes the file content for semaphore operations.
67
68        The value of the counter must remain in the range
69        [0, common.MAX_ADB_FASTBOOT_PROCESS] inclusive.
70        """
71        self.LockDevice(self._name, True, True)
72
73        try:
74            diff = common.MAX_ADB_FASTBOOT_PROCESS - self.sem_max_value
75            value_to_init = min(
76                max(0, self.sem_value + diff), common.MAX_ADB_FASTBOOT_PROCESS)
77        except IndexError:
78            value_to_init = common.MAX_ADB_FASTBOOT_PROCESS
79
80        self._lock_fd[self._name].seek(0)
81        self._lock_fd[self._name].truncate(0)
82        self._lock_fd[self._name].write(
83            "%s\n%s" % (common.MAX_ADB_FASTBOOT_PROCESS, value_to_init))
84
85        self.UnlockDevice(self._name)
86
87    @property
88    def sem_value(self):
89        """Reads the current counter value of the semaphore.
90
91        Returns:
92            int, the value of the current counter.
93        """
94        self._lock_fd[self._name].seek(0)
95        return int(self._lock_fd[self._name].read().split()[1])
96
97    @property
98    def sem_max_value(self):
99        """Reads the current maximum counter value of the semaphore.
100
101        Since the maximum counter value may vary at the deployment time,
102        the existing HC process group needs to look for the maximum value
103        every time it tries to access the semaphore
104
105        Returns:
106            int, the value of the maximum counter.
107        """
108        self._lock_fd[self._name].seek(0)
109        return int(self._lock_fd[self._name].read().split()[0])
110
111    def _Decrement(self):
112        """Decrements the internal counter of the semaphore."""
113        current_value = self.sem_value
114        current_max = self.sem_max_value
115        self._lock_fd[self._name].seek(len(str(current_max)) + 1)
116        self._lock_fd[self._name].write("%s" % max(0, (current_value - 1)))
117
118    def _Increment(self):
119        """Increments the internal counter of the semaphore."""
120        current_value = self.sem_value
121        current_max = self.sem_max_value
122        self._lock_fd[self._name].seek(len(str(current_max)) + 1)
123        self._lock_fd[self._name].write("%s" % min(current_max,
124                                                   (current_value + 1)))
125