Coverage for python/lsst/ctrl/orca/multithreading/SharedData.py: 12%

50 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-12 01:43 -0800

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23# 

24import threading 

25 

26 

27class SharedData: 

28 """A lock-protected container for data that can be shared amongst threads. 

29 

30 Parameters 

31 ---------- 

32 needLockOnRead : `bool` 

33 If true (default), acquiring the lock will be needed when reading the data. This 

34 is recommended if the data items are anything but primitive types; otherwise, 

35 a compound data item (e.g. of type dict) could be updated without acquiring a lock. 

36 data : `dict` 

37 A dictionary of data to initialize the container with. This is done by calling 

38 initData(). Set this value to False when calling from a subclass constructor; this 

39 will allow any non-protected attributes to be set via the subclass's constructor. If 

40 None is given (default), it is assumed that all new attributes will be considered protected data. 

41 cond : `bool` 

42 Reuse this existing Condition instance to protect this container 

43 

44 Notes 

45 ----- 

46 This container holds data that is intended to be shared amongst multiple 

47 threads. In order to update or (optionally) examine the data, one must 

48 first aquire the lock associated with the container. 

49 

50 This container also behaves like a threading.Container, via its 

51 wait(), notify(), and notifyAll() functions. Also like Condition, 

52 acquire() is reentrant. 

53 

54 SharedData instances may be used with the with statement: 

55 

56 sd = SharedData() 

57 with sd: 

58 sd.blah = 1 

59 

60 The with statement will acquire the lock and ensure that it is released 

61 when its block is exited. 

62 """ 

63 

64 def __init__(self, needLockOnRead=True, data=None, cond=None): 

65 self._d = {} 

66 if cond is None: 

67 cond = threading.Condition() 

68 self._cond = cond 

69 

70 # behave like a Condition 

71 

72 # acquire method 

73 self.acquire = cond.acquire 

74 

75 # release method 

76 self.release = cond.release 

77 

78 # notify method 

79 self.notify = cond.notify 

80 

81 # notifyall method 

82 self.notifyAll = cond.notifyAll 

83 

84 # wait method 

85 self.wait = cond.wait 

86 self._is_owned = cond._is_owned 

87 

88 self._lockOnRead = needLockOnRead 

89 

90 if isinstance(data, dict): 

91 self.initData(data) 

92 if data is None: 

93 self._d["__"] = True 

94 

95 # overrides __enter__ 

96 def __enter__(self, *args, **kwds): 

97 return self._cond.__enter__(*args, **kwds) 

98 

99 # overrides __exit__ 

100 def __exit__(self, *args, **kwds): 

101 return self._cond.__exit__(*args, **kwds) 

102 

103 # overrides __getattribute__ 

104 def __getattribute__(self, name): 

105 if name == "_d" or len(self._d) == 0 or name not in self._d: 

106 return object.__getattribute__(self, name) 

107 

108 if self._lockOnRead and not self._is_owned(): 

109 raise AttributeError("%s: lock required for read access" % name) 

110 return self._d[name] 

111 

112 # overrides __setattr__ 

113 def __setattr__(self, name, value): 

114 if name == "_d" or len(self._d) == 0 or name in self.__dict__: 

115 object.__setattr__(self, name, value) 

116 return 

117 

118 if not self._is_owned(): 

119 raise AttributeError("%s: lock required for write access" % name) 

120 

121 self._d[name] = value 

122 

123 def initData(self, data): 

124 """Initialize the container with the data from a dictionary. 

125 

126 Parameters 

127 ---------- 

128 data : `dict` 

129 A dictionary of data to initialize the container with. Attributes will be added to 

130 this container with names matching the given the dictionary's key names and 

131 initialized to their corresponding values. The keys cannot match an existing 

132 function (or internal attribute) name. 

133 

134 Raises 

135 ------ 

136 ValueError if the dictionary has a key that conflicts with an existing function or 

137 internal attribute name. 

138 """ 

139 with self._cond: 

140 bad = [] 

141 realattrs = self.__dict__ 

142 for key in data: 

143 if key in realattrs: 

144 bad.append(key) 

145 if len(bad) > 0: 

146 raise ValueError("Names cause conflicts with functions or " + 

147 "internal data: " + str(bad)) 

148 

149 for key in data: 

150 self._d[key] = data[key] 

151 

152 if len(self._d) == 0: 

153 self._d["__"] = True 

154 

155 # overrides dir() method 

156 def dir(self): 

157 return [k for k in self._d if k != "__"]