Skip to content

Commit 8bc72db

Browse files
author
FusionSolutions
committed
upload
1 parent 0176c1c commit 8bc72db

File tree

6 files changed

+227
-37
lines changed

6 files changed

+227
-37
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Signal capturer.
77

88
## Installation
99

10-
Requires python version 3.7 or later.
10+
Requires python version 3.8 or later.
1111

1212
To install the latest release on [PyPI](https://pypi.org/project/python-fssignal/),
1313
simply run:

fsSignal/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.0.9"
1+
__version__ = "0.1.0"
22
__doc__ = """
33
Signal capturer v{}
44
Copyright (C) 2021 Fusion Solutions KFT <contact@fusionsolutions.io>
@@ -16,6 +16,7 @@
1616
You should have received a copy of the GNU Lesser General Public License
1717
along with this program. If not, see <http://www.gnu.org/licenses/lgpl.txt>.
1818
""".format(__version__)
19-
from .fsSignal import KillSignal, SignalIterator, Signal, SoftSignal, HardSignal, BaseSignal, T_Signal
20-
21-
__all__ = "KillSignal", "SignalIterator", "Signal", "SoftSignal", "HardSignal", "BaseSignal", "T_Signal"
19+
from .abcs import T_Locker, T_Signal
20+
from .fsSignal import KillSignal, SignalIterator, Signal, SoftSignal, HardSignal, BaseSignal, SignalLocker, ExtendedLocker
21+
__all__ = ("KillSignal", "SignalIterator", "Signal", "SoftSignal", "HardSignal", "BaseSignal", "SignalLocker", "ExtendedLocker",
22+
"T_Locker", "T_Signal")

fsSignal/abcs.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Builtin modules
2+
from __future__ import annotations
3+
from abc import ABCMeta, abstractmethod
4+
from typing import Any, Iterable, Union
5+
# Third party modules
6+
# Local modules
7+
# Program
8+
class T_Lock(metaclass=ABCMeta):
9+
@abstractmethod
10+
def acquire(self, blocking:bool=..., timeout:float=...) -> bool: ...
11+
@abstractmethod
12+
def release(self) -> None: ...
13+
@abstractmethod
14+
def locked(self) -> bool: ...
15+
@abstractmethod
16+
def _is_owned(self) -> bool: ...
17+
@abstractmethod
18+
def __enter__(self) -> None: ...
19+
@abstractmethod
20+
def __exit__(self, type:Any, value:Any, traceback:Any) -> Any: ...
21+
22+
class T_Locker(metaclass=ABCMeta):
23+
lock:T_Lock
24+
@abstractmethod
25+
def acquire(self, blocking:bool=..., timeout:float=...) -> bool: ...
26+
@abstractmethod
27+
def release(self) -> None: ...
28+
@abstractmethod
29+
def owned(self) -> bool: ...
30+
@abstractmethod
31+
def locked(self) -> bool: ...
32+
@abstractmethod
33+
def __enter__(self) -> Any: ...
34+
@abstractmethod
35+
def __exit__(self, type:Any, value:Any, traceback:Any) -> Any: ...
36+
37+
class T_Signal(metaclass=ABCMeta):
38+
_force:bool
39+
@abstractmethod
40+
def get(self) -> bool: ...
41+
@abstractmethod
42+
def getSoft(self) -> bool: ...
43+
@abstractmethod
44+
def getHard(self) -> bool: ...
45+
@abstractmethod
46+
def check(self) -> None: ...
47+
@abstractmethod
48+
def checkSoft(self) -> None: ...
49+
@abstractmethod
50+
def checkHard(self) -> None: ...
51+
@abstractmethod
52+
def sleep(self, seconds:Union[int, float], raiseOnKill:bool=...) -> None: ...
53+
@abstractmethod
54+
def signalSoftKill(self, *args:Any, **kwargs:Any) -> None: ...
55+
@abstractmethod
56+
def signalHardKill(self, *args:Any, **kwargs:Any) -> None: ...
57+
@abstractmethod
58+
def iter(self, it:Iterable[Any], checkDelay:float=...) -> Iterable[Any]: ...
59+
@abstractmethod
60+
def softKill(self) -> None: ...
61+
@abstractmethod
62+
def hardKill(self) -> None: ...
63+
@abstractmethod
64+
def reset(self) -> None: ...
65+
@abstractmethod
66+
def getSoftSignal(self) -> T_Signal: ...
67+
@abstractmethod
68+
def getHardSignal(self) -> T_Signal: ...
69+
@abstractmethod
70+
def isActivated(self) -> bool: ...
71+
@abstractmethod
72+
def warpLock(self, lock:Any) -> T_Locker: ...

fsSignal/fsSignal.py

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import traceback, signal as _signal
44
from threading import Event
55
from time import monotonic, sleep
6-
from typing import Callable, Dict, Any, Iterator, Iterable, Optional, Union, Type
6+
from typing import Callable, Dict, Any, Iterator, Iterable, Optional, Union, cast
77
# Third party modules
88
# Local modules
9+
from .abcs import T_Signal, T_Locker, T_Lock
910
# Program
1011
class KillSignal(Exception): pass
1112

@@ -30,74 +31,117 @@ def __next__(self) -> Any:
3031
raise KillSignal
3132
return self.it.__next__()
3233

33-
class BaseSignal:
34-
_force:bool
35-
@classmethod
34+
class ExtendedLocker(T_Locker):
35+
def __init__(self, lock:T_Lock) -> None:
36+
self.lock = lock
37+
def __del__(self) -> None:
38+
if self.locked():
39+
self.release()
40+
def acquire(self, blocking:bool=True, timeout:float=-1.0) -> bool:
41+
return self.lock.acquire(blocking, timeout)
42+
def release(self) -> None:
43+
self.lock.release()
44+
def owned(self) -> bool:
45+
if hasattr(self.lock, "locked"):
46+
return self.lock.locked()
47+
return self.lock._is_owned()
48+
def locked(self) -> bool:
49+
if hasattr(self.lock, "locked"):
50+
return self.lock.locked()
51+
r = self.lock._is_owned()
52+
if r:
53+
return True
54+
r = self.lock.acquire(False)
55+
if r:
56+
self.lock.release()
57+
return False
58+
return True
59+
def __enter__(self) -> Any:
60+
self.lock.acquire()
61+
def __exit__(self, type:Any, value:Any, traceback:Any) -> Any:
62+
self.lock.release()
63+
64+
class SignalLocker(ExtendedLocker):
65+
event:Event
66+
def __init__(self, event:Event, lock:T_Lock) -> None:
67+
self.event = event
68+
self.lock = lock
69+
def acquire(self, blocking:bool=True, timeout:float=-1.0) -> bool:
70+
r:bool
71+
if blocking:
72+
if timeout is not None and timeout > 0:
73+
for _ in range(5):
74+
r = self.lock.acquire(True, timeout/5)
75+
if r:
76+
return r
77+
if self.event.is_set():
78+
raise KillSignal
79+
return False
80+
else:
81+
while True:
82+
if self.event.is_set():
83+
raise KillSignal
84+
r = self.lock.acquire(True, 1)
85+
if r:
86+
return r
87+
else:
88+
return self.lock.acquire(False)
89+
90+
class BaseSignal(T_Signal):
3691
def get(self) -> bool:
3792
if isinstance(Signal._handler, Signal):
3893
return Signal._handler._get(self._force)
3994
return False
40-
@classmethod
4195
def getSoft(self) -> bool:
4296
if isinstance(Signal._handler, Signal):
4397
return Signal._handler._get(False)
4498
return False
45-
@classmethod
4699
def getHard(self) -> bool:
47100
if isinstance(Signal._handler, Signal):
48101
return Signal._handler._get(True)
49102
return False
50-
@classmethod
51103
def check(self) -> None:
52104
if isinstance(Signal._handler, Signal):
53105
return Signal._handler._check(self._force)
54-
@classmethod
55106
def checkSoft(self) -> None:
56107
if isinstance(Signal._handler, Signal):
57108
return Signal._handler._check(False)
58-
@classmethod
59109
def checkHard(self) -> None:
60110
if isinstance(Signal._handler, Signal):
61111
return Signal._handler._check(True)
62-
@classmethod
63112
def sleep(self, seconds:Union[int, float], raiseOnKill:bool=False) -> None:
64113
if isinstance(Signal._handler, Signal):
65114
return Signal._handler._sleep(seconds, raiseOnKill, self._force)
66115
return sleep(seconds)
67-
@classmethod
68116
def signalSoftKill(self, *args:Any, **kwargs:Any) -> None:
69117
if isinstance(Signal._handler, Signal):
70118
return Signal._handler._signalSoftKill(*args, **kwargs)
71-
@classmethod
72119
def signalHardKill(self, *args:Any, **kwargs:Any) -> None:
73120
if isinstance(Signal._handler, Signal):
74121
return Signal._handler._signalHardKill(*args, **kwargs)
75-
@classmethod
76122
def iter(self, it:Iterable[Any], checkDelay:float=1.0) -> Iterable[Any]:
77123
if isinstance(Signal._handler, Signal):
78124
return Signal._handler._iter(it, checkDelay, self._force)
79125
return it
80-
@classmethod
81126
def softKill(self) -> None:
82127
if isinstance(Signal._handler, Signal):
83128
return Signal._handler._softKill()
84-
@classmethod
85129
def hardKill(self) -> None:
86130
if isinstance(Signal._handler, Signal):
87131
return Signal._handler._hardKill()
88-
@classmethod
89132
def reset(self) -> None:
90133
if isinstance(Signal._handler, Signal):
91134
return Signal._handler._reset()
92-
@classmethod
93-
def getSoftSignal(self) -> Type[BaseSignal]:
94-
return SoftSignal
95-
@classmethod
96-
def getHardSignal(self) -> Type[BaseSignal]:
97-
return HardSignal
98-
@classmethod
135+
def getSoftSignal(self) -> T_Signal:
136+
return SoftSignal()
137+
def getHardSignal(self) -> T_Signal:
138+
return HardSignal()
99139
def isActivated(self) -> bool:
100140
return isinstance(Signal._handler, Signal)
141+
def warpLock(self, lock:Any) -> T_Locker:
142+
if isinstance(Signal._handler, Signal):
143+
return Signal._handler._warpLock(lock, self._force)
144+
return ExtendedLocker(lock)
101145

102146
class SoftSignal(BaseSignal):
103147
_force:bool = False
@@ -143,8 +187,8 @@ def __setstate__(self, states:Dict[str, Any]) -> None:
143187
self.eHard = states["eHard"]
144188
self._activate()
145189
def _activate(self) -> None:
146-
_signal.signal(_signal.SIGINT, Signal.signalSoftKill)
147-
_signal.signal(_signal.SIGTERM, Signal.signalHardKill)
190+
_signal.signal(_signal.SIGINT, self.signalSoftKill)
191+
_signal.signal(_signal.SIGTERM, self.signalHardKill)
148192
def _get(self, force:bool=True) -> bool:
149193
if force:
150194
return self.eHard.is_set()
@@ -159,6 +203,8 @@ def _sleep(self, seconds:Union[int, float], raiseOnKill:bool=False, force:bool=T
159203
return None
160204
def _iter(self, it:Iterable[Any], checkDelay:float=1.0, force:bool=True) -> Iterator[Any]:
161205
return SignalIterator(self.eHard if force else self.eSoft, it, checkDelay)
206+
def _warpLock(self, lock:Any, force:bool=True) -> T_Locker:
207+
return SignalLocker(self.eHard if force else self.eSoft, cast(T_Lock, lock))
162208
def _signalSoftKill(self, *args:Any, **kwargs:Any) -> None:
163209
self._softKill()
164210
if not self.eHard.is_set():
@@ -193,5 +239,3 @@ def _reset(self) -> None:
193239
self.eSoft.clear()
194240
self.eHard.clear()
195241
self.counter = 0
196-
197-
T_Signal = Union[Signal, Type[BaseSignal]]

fsSignal/test/fsSignal.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Builtin modules
22
import os, unittest, signal as _signal
3-
from threading import Timer
3+
from threading import Lock, RLock, Timer, Thread
44
from time import monotonic, sleep
55
from typing import List
66
# Third party modules
77
# Local modules
8-
from .. import Signal, KillSignal
8+
from .. import Signal, KillSignal, T_Signal, SoftSignal, HardSignal
99
# Program
1010
class SignalTest(unittest.TestCase):
1111
rootSignal:Signal
@@ -63,3 +63,77 @@ def test_check(self) -> None:
6363
with self.assertRaises(KillSignal):
6464
signal.check()
6565
self.assertEqual(signal.get(), True)
66+
def test_lock(self) -> None:
67+
signal = self.rootSignal.getSoftSignal()
68+
locker = signal.warpLock(Lock())
69+
self.assertFalse(locker.owned())
70+
self.assertFalse(locker.locked())
71+
#
72+
self.assertTrue(locker.acquire())
73+
self.assertTrue(locker.owned())
74+
self.assertTrue(locker.locked())
75+
self.assertFalse(locker.acquire(timeout=0.1))
76+
#
77+
self.killmeTimer()
78+
with self.assertRaises(KillSignal):
79+
locker.acquire()
80+
locker.release()
81+
with self.assertRaises(RuntimeError):
82+
locker.release()
83+
return None
84+
def test_lockWith(self) -> None:
85+
signal = self.rootSignal.getSoftSignal()
86+
locker = signal.warpLock(Lock())
87+
with locker:
88+
self.assertFalse(locker.acquire(timeout=0.1))
89+
self.killmeTimer()
90+
with self.assertRaises(KillSignal):
91+
locker.acquire()
92+
return None
93+
def test_rlock(self) -> None:
94+
def AcquireAndSleep() -> None:
95+
with locker:
96+
sleep(3)
97+
signal = self.rootSignal.getSoftSignal()
98+
locker = signal.warpLock(RLock())
99+
self.assertFalse(locker.owned())
100+
self.assertFalse(locker.locked())
101+
#
102+
self.assertTrue(locker.acquire())
103+
self.assertTrue(locker.owned())
104+
self.assertTrue(locker.locked())
105+
self.assertTrue(locker.acquire(timeout=0.1))
106+
locker.release()
107+
locker.release()
108+
#
109+
self.assertFalse(locker.owned())
110+
self.assertFalse(locker.locked())
111+
thr = Thread(target=AcquireAndSleep, daemon=True)
112+
thr.start()
113+
signal.sleep(1)
114+
self.assertFalse(locker.owned())
115+
self.assertTrue(locker.locked())
116+
#
117+
self.killmeTimer()
118+
with self.assertRaises(KillSignal):
119+
locker.acquire()
120+
thr.join()
121+
with self.assertRaises(RuntimeError):
122+
locker.release()
123+
return None
124+
def test_rlockStress(self) -> None:
125+
signal = self.rootSignal.getSoftSignal()
126+
locker = signal.warpLock(RLock())
127+
for i in range(100):
128+
self.assertTrue(locker.acquire())
129+
self.assertTrue(locker.owned())
130+
self.assertTrue(locker.locked())
131+
locker.release()
132+
def test_types(self) -> None:
133+
signal:T_Signal
134+
signal = SoftSignal()
135+
signal = HardSignal()
136+
signal = self.rootSignal.getSoftSignal()
137+
signal = self.rootSignal.getHardSignal()
138+
signal = self.rootSignal
139+
signal

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
setup(
1111
name = "python-fssignal",
12-
version = "0.0.9",
12+
version = "0.1.0",
1313
description = "Signal capturer",
1414
keywords = "signal capture utility fusion solutions fusionsolutions",
1515
author = "Andor `iFA` Rajci - Fusions Solutions KFT",
@@ -20,14 +20,13 @@
2020
long_description = open(os.path.join(pwd, "README.md")).read(),
2121
long_description_content_type = "text/markdown",
2222
zip_safe = False,
23-
python_requires = ">=3.7.0",
23+
python_requires = ">=3.8.0",
2424
test_suite = "fsSignal.test",
2525
package_data = { "":["py.typed"] },
2626
classifiers = [ # https://pypi.org/pypi?%3Aaction=list_classifiers
2727
"Development Status :: 4 - Beta",
2828
"Topic :: Utilities",
2929
"Programming Language :: Python :: 3 :: Only",
30-
"Programming Language :: Python :: 3.7",
3130
"Programming Language :: Python :: 3.8",
3231
"Programming Language :: Python :: 3.9",
3332
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",

0 commit comments

Comments
 (0)