Formatted code following black style

This commit is contained in:
nocturn9x 2020-07-13 22:02:31 +02:00
parent 84f8ac5728
commit 3e4a87af21
8 changed files with 76 additions and 40 deletions

View File

@ -21,5 +21,11 @@ from .exceptions import GiambioError, AlreadyJoinedError, CancelledError
from ._traps import sleep from ._traps import sleep
from ._layers import Event from ._layers import Event
__all__ = ["AsyncScheduler", "GiambioError", "AlreadyJoinedError", "CancelledError", "sleep", "Event"] __all__ = [
"AsyncScheduler",
"GiambioError",
"AlreadyJoinedError",
"CancelledError",
"sleep",
"Event",
]

View File

@ -45,10 +45,14 @@ class AsyncScheduler:
self.tasks = deque() # Tasks that are ready to run self.tasks = deque() # Tasks that are ready to run
self.selector = DefaultSelector() # Selector object to perform I/O multiplexing self.selector = DefaultSelector() # Selector object to perform I/O multiplexing
self.current_task = None # This will always point to the currently running coroutine (Task object) self.current_task = None # This will always point to the currently running coroutine (Task object)
self.joined = {} # Maps child tasks that need to be joined their respective parent task self.joined = (
self.clock = default_timer # Monotonic clock to keep track of elapsed time reliably {}
) # Maps child tasks that need to be joined their respective parent task
self.clock = (
default_timer # Monotonic clock to keep track of elapsed time reliably
)
self.paused = TimeQueue(self.clock) # Tasks that are asleep self.paused = TimeQueue(self.clock) # Tasks that are asleep
self.events = {} # All Event objects self.events = {} # All Event objects
self.event_waiting = defaultdict(list) # Coroutines waiting on event objects self.event_waiting = defaultdict(list) # Coroutines waiting on event objects
self.sequence = 0 self.sequence = 0
@ -59,10 +63,14 @@ class AsyncScheduler:
give execution control to the loop itself.""" give execution control to the loop itself."""
while True: while True:
if not self.selector.get_map() and not any([self.paused, self.tasks, self.event_waiting]): # If there is nothing to do, just exit if not self.selector.get_map() and not any(
[self.paused, self.tasks, self.event_waiting]
): # If there is nothing to do, just exit
break break
if not self.tasks: if not self.tasks:
if self.paused: # If there are no actively running tasks, we try to schedule the asleep ones if (
self.paused
): # If there are no actively running tasks, we try to schedule the asleep ones
try: try:
self.check_sleeping() self.check_sleeping()
except BaseException as error: except BaseException as error:
@ -74,25 +82,31 @@ class AsyncScheduler:
except BaseException as error: except BaseException as error:
self.current_task.exc = error self.current_task.exc = error
self.reschedule_parent(self.current_task) self.reschedule_parent(self.current_task)
while self.tasks: # While there are tasks to run while self.tasks: # While there are tasks to run
self.current_task = self.tasks.popleft() # Sets the currently running task self.current_task = (
self.tasks.popleft()
) # Sets the currently running task
try: try:
if self.current_task.status == "cancel": # Deferred cancellation if self.current_task.status == "cancel": # Deferred cancellation
self.current_task.cancelled = True self.current_task.cancelled = True
self.current_task.throw(CancelledError(self.current_task)) self.current_task.throw(CancelledError(self.current_task))
method, *args = self.current_task.run(self.current_task._notify) # Run a single step with the calculation (and awake event-waiting tasks if any) method, *args = self.current_task.run(
self.current_task._notify
) # Run a single step with the calculation (and awake event-waiting tasks if any)
self.current_task.status = "run" self.current_task.status = "run"
getattr(self, method)(*args) # Sneaky method call, thanks to David Beazley for this ;) getattr(self, method)(
*args
) # Sneaky method call, thanks to David Beazley for this ;)
if self.event_waiting: if self.event_waiting:
self.check_events() self.check_events()
except CancelledError as cancelled: except CancelledError as cancelled:
self.tasks.remove(cancelled.args[0]) # Remove the dead task self.tasks.remove(cancelled.args[0]) # Remove the dead task
self.tasks.append(self.current_task) self.tasks.append(self.current_task)
except StopIteration as e: # Coroutine ends except StopIteration as e: # Coroutine ends
self.current_task.result = e.args[0] if e.args else None self.current_task.result = e.args[0] if e.args else None
self.current_task.finished = True self.current_task.finished = True
self.reschedule_parent(self.current_task) self.reschedule_parent(self.current_task)
except BaseException as error: # Coroutine raised except BaseException as error: # Coroutine raised
self.current_task.exc = error self.current_task.exc = error
self.reschedule_parent(self.current_task) self.reschedule_parent(self.current_task)
@ -110,8 +124,12 @@ class AsyncScheduler:
def check_sleeping(self): def check_sleeping(self):
"""Checks and reschedules sleeping tasks""" """Checks and reschedules sleeping tasks"""
wait(max(0.0, self.paused[0][0] - self.clock())) # Sleep until the closest deadline in order not to waste CPU cycles wait(
while self.paused[0][0] < self.clock(): # Reschedules tasks when their deadline has elapsed max(0.0, self.paused[0][0] - self.clock())
) # Sleep until the closest deadline in order not to waste CPU cycles
while (
self.paused[0][0] < self.clock()
): # Reschedules tasks when their deadline has elapsed
self.tasks.append(self.paused.get()) self.tasks.append(self.paused.get())
if not self.paused: if not self.paused:
break break
@ -119,8 +137,12 @@ class AsyncScheduler:
def check_io(self): def check_io(self):
"""Checks and schedules task to perform I/O""" """Checks and schedules task to perform I/O"""
timeout = 0.0 if self.tasks else None # If there are no tasks ready wait indefinitely timeout = (
io_ready = self.selector.select(timeout) # Get sockets that are ready and schedule their tasks 0.0 if self.tasks else None
) # If there are no tasks ready wait indefinitely
io_ready = self.selector.select(
timeout
) # Get sockets that are ready and schedule their tasks
for key, _ in io_ready: for key, _ in io_ready:
self.tasks.append(key.data) # Socket ready? Schedule the task self.tasks.append(key.data) # Socket ready? Schedule the task
@ -167,7 +189,7 @@ class AsyncScheduler:
self.current_task.status = "I/O" self.current_task.status = "I/O"
if self.current_task._last_io: if self.current_task._last_io:
if self.current_task._last_io == ("READ", sock): if self.current_task._last_io == ("READ", sock):
return # Socket is already scheduled! return # Socket is already scheduled!
else: else:
self.selector.unregister(sock) self.selector.unregister(sock)
busy = False busy = False
@ -185,7 +207,7 @@ class AsyncScheduler:
self.current_task.status = "I/O" self.current_task.status = "I/O"
if self.current_task._last_io: if self.current_task._last_io:
if self.current_task._last_io == ("WRITE", sock): if self.current_task._last_io == ("WRITE", sock):
return # Socket is already scheduled! return # Socket is already scheduled!
else: else:
self.selector.unregister(sock) # modify() causes issues self.selector.unregister(sock) # modify() causes issues
busy = False busy = False
@ -205,7 +227,7 @@ class AsyncScheduler:
if child.cancelled: # Task was cancelled and is therefore dead if child.cancelled: # Task was cancelled and is therefore dead
self.tasks.append(self.current_task) self.tasks.append(self.current_task)
elif child.exc: # Task raised an error, propagate it! elif child.exc: # Task raised an error, propagate it!
self.reschedule_parent(child) self.reschedule_parent(child)
raise child.exc raise child.exc
elif child.finished: elif child.finished:
@ -214,7 +236,9 @@ class AsyncScheduler:
if child not in self.joined: if child not in self.joined:
self.joined[child] = self.current_task self.joined[child] = self.current_task
else: else:
raise AlreadyJoinedError("Joining the same task multiple times is not allowed!") raise AlreadyJoinedError(
"Joining the same task multiple times is not allowed!"
)
def sleep(self, seconds: int or float): def sleep(self, seconds: int or float):
"""Puts the caller to sleep for a given amount of seconds""" """Puts the caller to sleep for a given amount of seconds"""
@ -250,11 +274,13 @@ class AsyncScheduler:
in order to stop it from executing. The loop continues to execute as tasks in order to stop it from executing. The loop continues to execute as tasks
are independent""" are independent"""
if task.status in ("sleep", "I/O") and not task.cancelled: # It is safe to cancel a task while blocking if (
task.status in ("sleep", "I/O") and not task.cancelled
): # It is safe to cancel a task while blocking
task.cancelled = True task.cancelled = True
task.throw(CancelledError(task)) task.throw(CancelledError(task))
elif task.status == "run": elif task.status == "run":
task.status = "cancel" # Cancellation is deferred task.status = "cancel" # Cancellation is deferred
def wrap_socket(self, sock): def wrap_socket(self, sock):
"""Wraps a standard socket into an AsyncSocket object""" """Wraps a standard socket into an AsyncSocket object"""
@ -300,4 +326,4 @@ class AsyncScheduler:
await want_write(sock) await want_write(sock)
err = sock.getsockopt(SOL_SOCKET, SO_ERROR) err = sock.getsockopt(SOL_SOCKET, SO_ERROR)
if err != 0: if err != 0:
raise OSError(err, f'Connect call failed: {addr}') raise OSError(err, f"Connect call failed: {addr}")

View File

@ -26,7 +26,7 @@ class Task:
def __init__(self, coroutine: types.coroutine): def __init__(self, coroutine: types.coroutine):
self.coroutine = coroutine self.coroutine = coroutine
self.cancelled = False # True if the task gets cancelled self.cancelled = False # True if the task gets cancelled
self.exc = None self.exc = None
self.result = None self.result = None
self.finished = False self.finished = False
@ -117,5 +117,3 @@ class TimeQueue:
def get(self): def get(self):
return heappop(self.container)[2] return heappop(self.container)[2]

View File

@ -14,8 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
""" """
class GiambioError(Exception): class GiambioError(Exception):
"""Base class for gaimbio exceptions""" """Base class for gaimbio exceptions"""
pass pass
@ -33,14 +35,17 @@ class CancelledError(BaseException):
class ResourceBusy(GiambioError): class ResourceBusy(GiambioError):
"""Exception that is raised when a resource is accessed by more than """Exception that is raised when a resource is accessed by more than
one task at a time""" one task at a time"""
pass pass
class BrokenPipeError(GiambioError): class BrokenPipeError(GiambioError):
"""Wrapper around the broken pipe socket.error""" """Wrapper around the broken pipe socket.error"""
pass pass
class ResourceClosed(GiambioError): class ResourceClosed(GiambioError):
"""Raised when I/O is attempted on a closed fd""" """Raised when I/O is attempted on a closed fd"""
pass pass

View File

@ -21,8 +21,10 @@ limitations under the License.
import socket import socket
from .exceptions import ResourceClosed from .exceptions import ResourceClosed
from ._traps import sleep from ._traps import sleep
try: try:
from ssl import SSLWantReadError, SSLWantWriteError from ssl import SSLWantReadError, SSLWantWriteError
WantRead = (BlockingIOError, InterruptedError, SSLWantReadError) WantRead = (BlockingIOError, InterruptedError, SSLWantReadError)
WantWrite = (BlockingIOError, InterruptedError, SSLWantWriteError) WantWrite = (BlockingIOError, InterruptedError, SSLWantWriteError)
except ImportError: except ImportError:
@ -67,7 +69,7 @@ class AsyncSocket(object):
if self._closed: if self._closed:
raise ResourceClosed("I/O operation on closed socket") raise ResourceClosed("I/O operation on closed socket")
await sleep(0) # Give the scheduler the time to unregister the socket first await sleep(0) # Give the scheduler the time to unregister the socket first
await self.loop.close_sock(self.sock) await self.loop.close_sock(self.sock)
self._closed = True self._closed = True
@ -86,4 +88,3 @@ class AsyncSocket(object):
def __repr__(self): def __repr__(self):
return f"giambio.socket.AsyncSocket({self.sock}, {self.loop})" return f"giambio.socket.AsyncSocket({self.sock}, {self.loop})"

View File

@ -16,7 +16,7 @@ setuptools.setup(
classifiers=[ classifiers=[
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"License :: OSI Approved :: Apache License 2.0" "License :: OSI Approved :: Apache License 2.0",
], ],
python_requires='>=3.6' python_requires=">=3.6",
) )

View File

@ -9,6 +9,7 @@ async def countdown(n: int):
print("Countdown over") print("Countdown over")
return 0 return 0
async def countup(stop: int, step: int = 1): async def countup(stop: int, step: int = 1):
x = 0 x = 0
while x < stop: while x < stop:
@ -32,7 +33,7 @@ async def main():
print(f"Countup returned: {up}\nCountdown returned: {down}") print(f"Countup returned: {up}\nCountdown returned: {down}")
print("Task execution complete") print("Task execution complete")
if __name__ == "__main__": if __name__ == "__main__":
scheduler = giambio.AsyncScheduler() scheduler = giambio.AsyncScheduler()
scheduler.start(main()) scheduler.start(main())

View File

@ -5,9 +5,9 @@ import logging
sched = giambio.AsyncScheduler() sched = giambio.AsyncScheduler()
logging.basicConfig(level=20, logging.basicConfig(
format="[%(levelname)s] %(asctime)s %(message)s", level=20, format="[%(levelname)s] %(asctime)s %(message)s", datefmt="%d/%m/%Y %p"
datefmt='%d/%m/%Y %p') )
async def server(address: tuple): async def server(address: tuple):
@ -31,7 +31,7 @@ async def echo_handler(sock: AsyncSocket, addr: tuple):
if not data: if not data:
break break
to_send_back = data to_send_back = data
data = data.decode("utf-8").encode('unicode_escape') data = data.decode("utf-8").encode("unicode_escape")
logging.info(f"Got: '{data.decode('utf-8')}' from {addr}") logging.info(f"Got: '{data.decode('utf-8')}' from {addr}")
await sock.send_all(b"Got: " + to_send_back) await sock.send_all(b"Got: " + to_send_back)
logging.info(f"Echoed back '{data.decode('utf-8')}' to {addr}") logging.info(f"Echoed back '{data.decode('utf-8')}' to {addr}")
@ -40,7 +40,6 @@ async def echo_handler(sock: AsyncSocket, addr: tuple):
if __name__ == "__main__": if __name__ == "__main__":
try: try:
sched.start(server(('', 25001))) sched.start(server(("", 25001)))
except KeyboardInterrupt: # Exceptions propagate! except KeyboardInterrupt: # Exceptions propagate!
print("Exiting...") print("Exiting...")