mirror of https://github.com/nocturn9x/giambio.git
Starting to work on async pools
This commit is contained in:
parent
5bfc12fc73
commit
7b4051f3b9
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
__author__ = "Nocturn9x aka Isgiambyy"
|
__author__ = "Nocturn9x aka Isgiambyy"
|
||||||
__version__ = (1, 0, 0)
|
__version__ = (1, 0, 0)
|
||||||
from ._run import run, spawn, clock, wrap_socket
|
from ._run import run, clock, wrap_socket, create_pool
|
||||||
from .exceptions import GiambioError, AlreadyJoinedError, CancelledError
|
from .exceptions import GiambioError, AlreadyJoinedError, CancelledError
|
||||||
from ._traps import sleep
|
from ._traps import sleep
|
||||||
from ._layers import Event
|
from ._layers import Event
|
||||||
|
@ -28,7 +28,7 @@ __all__ = [
|
||||||
"sleep",
|
"sleep",
|
||||||
"Event",
|
"Event",
|
||||||
"run",
|
"run",
|
||||||
"spawn",
|
|
||||||
"clock",
|
"clock",
|
||||||
"wrap_socket"
|
"wrap_socket",
|
||||||
|
"create_pool"
|
||||||
]
|
]
|
||||||
|
|
122
giambio/_core.py
122
giambio/_core.py
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
# Import libraries and internal resources
|
# Import libraries and internal resources
|
||||||
import types
|
import types
|
||||||
from collections import deque, defaultdict
|
from collections import defaultdict
|
||||||
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
|
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
|
||||||
import socket
|
import socket
|
||||||
from .exceptions import AlreadyJoinedError, CancelledError, ResourceBusy, GiambioError
|
from .exceptions import AlreadyJoinedError, CancelledError, ResourceBusy, GiambioError
|
||||||
|
@ -42,9 +42,10 @@ class AsyncScheduler:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Object constructor"""
|
"""Object constructor"""
|
||||||
|
|
||||||
self.tasks = deque() # Tasks that are ready to run
|
self.tasks = [] # 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.catch = True
|
||||||
self.joined = (
|
self.joined = (
|
||||||
{}
|
{}
|
||||||
) # Maps child tasks that need to be joined their respective parent task
|
) # Maps child tasks that need to be joined their respective parent task
|
||||||
|
@ -53,7 +54,7 @@ class AsyncScheduler:
|
||||||
)
|
)
|
||||||
self.paused = TimeQueue(self.clock) # Tasks that are asleep
|
self.paused = TimeQueue(self.clock) # Tasks that are asleep
|
||||||
self.events = set() # All Event objects
|
self.events = set() # 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
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
|
@ -67,32 +68,31 @@ class AsyncScheduler:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if not self.selector.get_map() and not any(
|
if not self.selector.get_map() and not any(
|
||||||
[self.paused, self.tasks, self._event_waiting]
|
[self.paused, self.tasks, self.event_waiting]
|
||||||
): # If there is nothing to do, just exit
|
): # If there is nothing to do, just exit
|
||||||
break
|
break
|
||||||
if not self.tasks:
|
elif not self.tasks:
|
||||||
if (
|
if self.paused:
|
||||||
self.paused
|
# If there are no actively running tasks, we try to schedule the asleep ones
|
||||||
): # If there are no actively running tasks, we try to schedule the asleep ones
|
|
||||||
self._check_sleeping()
|
self._check_sleeping()
|
||||||
if self.selector.get_map():
|
if self.selector.get_map():
|
||||||
self._check_io() # The next step is checking for I/O
|
self._check_io() # The next step is checking for I/O
|
||||||
|
if self.event_waiting:
|
||||||
|
# Try to awake event-waiting tasks
|
||||||
|
self._check_events()
|
||||||
while self.tasks: # While there are tasks to run
|
while self.tasks: # While there are tasks to run
|
||||||
self.current_task = (
|
self.current_task = self.tasks.pop(0)
|
||||||
self.tasks.popleft()
|
# Sets the currently running task
|
||||||
) # Sets the currently running task
|
|
||||||
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() # Run a single step with the calculation
|
method, *args = self.current_task.run() # Run a single step with the calculation
|
||||||
self.current_task.status = "run"
|
self.current_task.status = "run"
|
||||||
getattr(self, f"_{method}")(
|
getattr(self, f"_{method}")(*args)
|
||||||
*args
|
# Sneaky method call, thanks to David Beazley for this ;)
|
||||||
) # Sneaky method call, thanks to David Beazley for this ;)
|
|
||||||
if self._event_waiting:
|
|
||||||
self._check_events()
|
|
||||||
except CancelledError as cancelled:
|
except CancelledError as cancelled:
|
||||||
self.tasks.remove(cancelled.args[0]) # Remove the dead task
|
if cancelled.args[0] in self.tasks:
|
||||||
|
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
|
||||||
|
@ -100,42 +100,33 @@ class AsyncScheduler:
|
||||||
self._reschedule_parent()
|
self._reschedule_parent()
|
||||||
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()
|
if self.catch:
|
||||||
self._join(self.current_task)
|
self._reschedule_parent()
|
||||||
|
self._join(self.current_task)
|
||||||
def clock(self):
|
else:
|
||||||
"""
|
if not isinstance(error, RuntimeError):
|
||||||
Returns the current clock time for the event loop.
|
raise
|
||||||
Useful to keep track of elapsed time in the terms of
|
|
||||||
the scheduler itself
|
|
||||||
:return: whatever self.clock returns
|
|
||||||
:rtype:
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.clock()
|
|
||||||
|
|
||||||
def _check_events(self):
|
def _check_events(self):
|
||||||
"""
|
"""
|
||||||
Checks for ready or expired events and triggers them
|
Checks for ready or expired events and triggers them
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for event, tasks in self._event_waiting.copy().items():
|
for event, tasks in self.event_waiting.copy().items():
|
||||||
if event._set:
|
if event._set:
|
||||||
event.event_caught = True
|
event.event_caught = True
|
||||||
self.tasks.extend(tasks + [event.notifier])
|
self.tasks.extend(tasks + [event.notifier])
|
||||||
self._event_waiting.pop(event)
|
self.event_waiting.pop(event)
|
||||||
|
|
||||||
def _check_sleeping(self):
|
def _check_sleeping(self):
|
||||||
"""
|
"""
|
||||||
Checks and reschedules sleeping tasks
|
Checks and reschedules sleeping tasks
|
||||||
"""
|
"""
|
||||||
|
|
||||||
wait(
|
wait(max(0.0, self.paused[0][0] - self.clock()))
|
||||||
max(0.0, self.paused[0][0] - self.clock())
|
# Sleep until the closest deadline in order not to waste CPU cycles
|
||||||
) # Sleep until the closest deadline in order not to waste CPU cycles
|
while self.paused[0][0] < self.clock():
|
||||||
while (
|
# Reschedules tasks when their deadline has elapsed
|
||||||
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
|
||||||
|
@ -145,41 +136,22 @@ class AsyncScheduler:
|
||||||
Checks and schedules task to perform I/O
|
Checks and schedules task to perform I/O
|
||||||
"""
|
"""
|
||||||
|
|
||||||
timeout = (
|
timeout = 0.0 if self.tasks else None
|
||||||
0.0 if self.tasks else None
|
# If there are no tasks ready wait indefinitely
|
||||||
) # If there are no tasks ready wait indefinitely
|
io_ready = self.selector.select(timeout)
|
||||||
io_ready = self.selector.select(
|
# Get sockets that are ready and schedule their tasks
|
||||||
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) # Resource ready? Schedule its task
|
||||||
|
|
||||||
def spawn(self, func: types.FunctionType, *args):
|
|
||||||
"""
|
|
||||||
Spawns a child task
|
|
||||||
"""
|
|
||||||
|
|
||||||
task = Task(func(*args))
|
|
||||||
self.tasks.append(task)
|
|
||||||
return task
|
|
||||||
|
|
||||||
def spawn_after(self, func: types.FunctionType, n: int, *args):
|
|
||||||
"""
|
|
||||||
Schedules a task for execution after n seconds
|
|
||||||
"""
|
|
||||||
|
|
||||||
task = Task(func(*args))
|
|
||||||
self.paused.put(task, n)
|
|
||||||
return task
|
|
||||||
|
|
||||||
def start(self, func: types.FunctionType, *args):
|
def start(self, func: types.FunctionType, *args):
|
||||||
"""
|
"""
|
||||||
Starts the event loop using a coroutine as an entry point.
|
Starts the event loop from a sync context
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entry = self.spawn(func, *args)
|
entry = Task(func(*args))
|
||||||
self._run()
|
self.tasks.append(entry)
|
||||||
self._join(entry)
|
self._join(entry)
|
||||||
|
self._run()
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
def _reschedule_parent(self):
|
def _reschedule_parent(self):
|
||||||
|
@ -236,12 +208,9 @@ class AsyncScheduler:
|
||||||
parent task
|
parent task
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if child.cancelled or child.finished: # Task was cancelled or has finished executing and is therefore dead
|
if child.cancelled or child.exc: # Task was cancelled or has errored
|
||||||
self._reschedule_parent()
|
self._reschedule_parent()
|
||||||
elif child.exc: # Task raised an error, propagate it!
|
elif child.finished: # Task finished running
|
||||||
self._reschedule_parent()
|
|
||||||
raise child.exc
|
|
||||||
elif child.finished:
|
|
||||||
self.tasks.append(self.current_task) # Task has already finished
|
self.tasks.append(self.current_task) # Task has already finished
|
||||||
else:
|
else:
|
||||||
if child not in self.joined:
|
if child not in self.joined:
|
||||||
|
@ -283,7 +252,7 @@ class AsyncScheduler:
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self._event_waiting[event].append(self.current_task)
|
self.event_waiting[event].append(self.current_task)
|
||||||
|
|
||||||
def _cancel(self, task):
|
def _cancel(self, task):
|
||||||
"""
|
"""
|
||||||
|
@ -292,9 +261,8 @@ class AsyncScheduler:
|
||||||
are independent
|
are independent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if (
|
if task.status in ("sleep", "I/O") and not task.cancelled:
|
||||||
task.status in ("sleep", "I/O") and not task.cancelled
|
# It is safe to cancel a task while blocking
|
||||||
): # 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":
|
||||||
|
|
|
@ -46,12 +46,21 @@ class Task:
|
||||||
async def join(self):
|
async def join(self):
|
||||||
"""Joins the task"""
|
"""Joins the task"""
|
||||||
|
|
||||||
return await join(self)
|
if self.cancelled and not self.exc:
|
||||||
|
return None
|
||||||
|
if self.exc:
|
||||||
|
raise self.exc
|
||||||
|
res = await join(self)
|
||||||
|
if self.exc:
|
||||||
|
raise self.exc
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
async def cancel(self):
|
async def cancel(self):
|
||||||
"""Cancels the task"""
|
"""Cancels the task"""
|
||||||
|
|
||||||
await cancel(self)
|
await cancel(self)
|
||||||
|
assert self.cancelled, "Task ignored cancellation"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""Implements repr(self)"""
|
"""Implements repr(self)"""
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""
|
||||||
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ._core import AsyncScheduler
|
||||||
|
from ._layers import Task
|
||||||
|
import types
|
||||||
|
|
||||||
|
|
||||||
|
class TaskManager:
|
||||||
|
"""
|
||||||
|
An asynchronous context manager for giambio
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, loop: AsyncScheduler) -> None:
|
||||||
|
"""
|
||||||
|
Object constructor
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.loop = loop
|
||||||
|
|
||||||
|
def spawn(self, func: types.FunctionType, *args):
|
||||||
|
"""
|
||||||
|
Spawns a child task
|
||||||
|
"""
|
||||||
|
|
||||||
|
task = Task(func(*args))
|
||||||
|
self.loop.tasks.append(task)
|
||||||
|
return task
|
||||||
|
|
||||||
|
def spawn_after(self, func: types.FunctionType, n: int, *args):
|
||||||
|
"""
|
||||||
|
Schedules a task for execution after n seconds
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert n >= 0, "The time delay can't be negative"
|
||||||
|
task = Task(func(*args))
|
||||||
|
self.loop.paused.put(task, n)
|
||||||
|
return task
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
self.loop.catch = True # Restore event loop's status
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
|
for task in self.loop.tasks:
|
||||||
|
try:
|
||||||
|
await task.join()
|
||||||
|
except BaseException as e:
|
||||||
|
for task in self.loop.tasks:
|
||||||
|
await task.cancel()
|
||||||
|
for _, __, task in self.loop.paused:
|
||||||
|
await task.cancel()
|
||||||
|
for tasks in self.loop.event_waiting.values():
|
||||||
|
for task in tasks:
|
||||||
|
await task.cancel()
|
||||||
|
self.loop.catch = False
|
||||||
|
raise e
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import threading
|
import threading
|
||||||
from ._core import AsyncScheduler
|
from ._core import AsyncScheduler
|
||||||
from ._layers import Task
|
from ._layers import Task
|
||||||
|
from ._managers import TaskManager
|
||||||
from .socket import AsyncSocket
|
from .socket import AsyncSocket
|
||||||
from types import FunctionType, CoroutineType, GeneratorType
|
from types import FunctionType, CoroutineType, GeneratorType
|
||||||
import socket
|
import socket
|
||||||
|
@ -33,7 +34,9 @@ def run(func: FunctionType, *args) -> Task:
|
||||||
if isinstance(func, (CoroutineType, GeneratorType)):
|
if isinstance(func, (CoroutineType, GeneratorType)):
|
||||||
raise RuntimeError("Looks like you tried to call giambio.run(your_func(arg1, arg2, ...)), that is wrong!"
|
raise RuntimeError("Looks like you tried to call giambio.run(your_func(arg1, arg2, ...)), that is wrong!"
|
||||||
"\nWhat you wanna do, instead, is this: giambio.run(your_func, arg1, arg2, ...)")
|
"\nWhat you wanna do, instead, is this: giambio.run(your_func, arg1, arg2, ...)")
|
||||||
if not hasattr(thread_local, "loop"):
|
try:
|
||||||
|
return thread_local.loop.start(func, *args)
|
||||||
|
except AttributeError:
|
||||||
thread_local.loop = AsyncScheduler()
|
thread_local.loop = AsyncScheduler()
|
||||||
return thread_local.loop.start(func, *args)
|
return thread_local.loop.start(func, *args)
|
||||||
|
|
||||||
|
@ -47,25 +50,21 @@ def clock():
|
||||||
return thread_local.loop.clock()
|
return thread_local.loop.clock()
|
||||||
|
|
||||||
|
|
||||||
def spawn(func: FunctionType, *args):
|
|
||||||
"""
|
|
||||||
Spawns a child task in the current event
|
|
||||||
loop
|
|
||||||
"""
|
|
||||||
|
|
||||||
if isinstance(func, (CoroutineType, GeneratorType)):
|
|
||||||
raise RuntimeError("Looks like you tried to call giambio.spawn(your_func(arg1, arg2, ...)), that is wrong!"
|
|
||||||
"\nWhat you wanna do, instead, is this: giambio.spawn(your_func, arg1, arg2, ...)")
|
|
||||||
try:
|
|
||||||
return thread_local.loop.spawn(func, *args)
|
|
||||||
except AttributeError:
|
|
||||||
raise RuntimeError("It appears that giambio is not running, did you call giambio.spawn(...)"
|
|
||||||
" outside of an async context?") from None
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_socket(sock: socket.socket) -> AsyncSocket:
|
def wrap_socket(sock: socket.socket) -> AsyncSocket:
|
||||||
"""
|
"""
|
||||||
Wraps a synchronous socket into a giambio.socket.AsyncSocket
|
Wraps a synchronous socket into a giambio.socket.AsyncSocket
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return thread_local.loop.wrap_socket(sock)
|
return thread_local.loop.wrap_socket(sock)
|
||||||
|
|
||||||
|
|
||||||
|
def create_pool():
|
||||||
|
"""
|
||||||
|
Creates an async pool
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return TaskManager(thread_local.loop)
|
||||||
|
except AttributeError:
|
||||||
|
raise RuntimeError("It appears that giambio is not running, did you call giambio.async_pool()"
|
||||||
|
" outside of an async context?") from None
|
||||||
|
|
|
@ -39,6 +39,7 @@ def sleep(seconds: int):
|
||||||
:type seconds: int
|
:type seconds: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
assert seconds >= 0, "The time delay can't be negative"
|
||||||
yield "sleep", seconds
|
yield "sleep", seconds
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class AlreadyJoinedError(GiambioError):
|
||||||
|
|
||||||
|
|
||||||
class CancelledError(BaseException):
|
class CancelledError(BaseException):
|
||||||
"""Exception raised as a result of the giambio.core.cancel() method"""
|
"""Exception raised by the giambio._layers.Task.cancel() method"""
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "giambio.exceptions.CancelledError"
|
return "giambio.exceptions.CancelledError"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import giambio
|
import giambio
|
||||||
|
|
||||||
|
|
||||||
# A test for cancellation
|
# A test for context managers
|
||||||
|
|
||||||
|
|
||||||
async def countdown(n: int):
|
async def countdown(n: int):
|
||||||
|
@ -9,38 +9,43 @@ async def countdown(n: int):
|
||||||
print(f"Down {n}")
|
print(f"Down {n}")
|
||||||
n -= 1
|
n -= 1
|
||||||
await giambio.sleep(1)
|
await giambio.sleep(1)
|
||||||
|
# raise Exception("oh no man") # Uncomment to test propagation
|
||||||
print("Countdown over")
|
print("Countdown over")
|
||||||
# raise Exception("oh no man")
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
async def countup(stop: int, step: int = 1):
|
async def countup(stop: int, step: int = 1):
|
||||||
x = 0
|
try:
|
||||||
while x < stop:
|
x = 0
|
||||||
print(f"Up {x}")
|
while x < stop:
|
||||||
x += 1
|
print(f"Up {x}")
|
||||||
await giambio.sleep(step)
|
x += 1
|
||||||
print("Countup over")
|
await giambio.sleep(step)
|
||||||
return 1
|
print("Countup over")
|
||||||
|
return 1
|
||||||
|
except giambio.exceptions.CancelledError:
|
||||||
|
print("I'm not gonna die!!")
|
||||||
|
raise BaseException(2)
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
cdown = giambio.spawn(countdown, 10)
|
try:
|
||||||
cup = giambio.spawn(countup, 5, 2)
|
print("Creating an async pool")
|
||||||
print("Counters started, awaiting completion")
|
async with giambio.create_pool() as pool:
|
||||||
await giambio.sleep(2)
|
print("Starting counters")
|
||||||
print("Slept 2 seconds, killing countup")
|
pool.spawn(countdown, 10)
|
||||||
await cup.cancel()
|
t = pool.spawn(countup, 5, 2)
|
||||||
# raise TypeError("bruh")
|
await giambio.sleep(2)
|
||||||
print("Countup cancelled")
|
await t.cancel()
|
||||||
up = await cup.join()
|
print("Task execution complete")
|
||||||
down = await cdown.join()
|
except Exception as e:
|
||||||
print(f"Countup returned: {up}\nCountdown returned: {down}")
|
print(f"Caught this bad boy in here, propagating it -> {type(e).__name__}: {e}")
|
||||||
print("Task execution complete")
|
raise
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
print("Starting event loop")
|
||||||
try:
|
try:
|
||||||
giambio.run(main)
|
giambio.run(main)
|
||||||
except Exception as e:
|
except BaseException as e:
|
||||||
print(f"Exception caught! -> {type(e).__name__}: {e}")
|
print(f"Exception caught from main event loop!! -> {type(e).__name__}: {e}")
|
||||||
|
print("Event loop done")
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import giambio
|
import giambio
|
||||||
import traceback
|
|
||||||
from giambio.socket import AsyncSocket
|
from giambio.socket import AsyncSocket
|
||||||
import socket
|
import socket
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
# A test to check for asynchronous I/O
|
# A test to check for asynchronous I/O
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
|
@ -20,10 +20,13 @@ async def server(address: tuple):
|
||||||
asock = giambio.wrap_socket(sock) # We make the socket an async socket
|
asock = giambio.wrap_socket(sock) # We make the socket an async socket
|
||||||
logging.info(f"Echo server serving asynchronously at {address}")
|
logging.info(f"Echo server serving asynchronously at {address}")
|
||||||
while True:
|
while True:
|
||||||
conn, addr = await asock.accept()
|
try:
|
||||||
logging.info(f"{addr} connected")
|
async with giambio.async_pool() as pool:
|
||||||
task = giambio.spawn(echo_handler, conn, addr)
|
conn, addr = await asock.accept()
|
||||||
# await task.join() # TODO: Joining I/O tasks seems broken
|
logging.info(f"{addr} connected")
|
||||||
|
pool.spawn(echo_handler, conn, addr)
|
||||||
|
except TypeError:
|
||||||
|
print("Looks like we have a naughty boy here!")
|
||||||
|
|
||||||
|
|
||||||
async def echo_handler(sock: AsyncSocket, addr: tuple):
|
async def echo_handler(sock: AsyncSocket, addr: tuple):
|
||||||
|
@ -46,9 +49,11 @@ async def echo_handler(sock: AsyncSocket, addr: tuple):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
port = int(sys.argv[1])
|
||||||
|
else:
|
||||||
|
port = 1500
|
||||||
try:
|
try:
|
||||||
giambio.run(server, ("", 1501))
|
giambio.run(server, ("", port))
|
||||||
except BaseException as error: # Exceptions propagate!
|
except (Exception, KeyboardInterrupt) as error: # Exceptions propagate!
|
||||||
print(f"Exiting due to a {type(error).__name__}: '{error}'", end=" ")
|
print(f"Exiting due to a {type(error).__name__}: '{error}'")
|
||||||
print("traceback below (or above, or in the middle, idk async is weird)")
|
|
||||||
traceback.print_exception(*sys.exc_info())
|
|
||||||
|
|
Loading…
Reference in New Issue