Added a small README and divided the lib into multiple files

This commit is contained in:
nocturn9x 2020-03-24 13:19:10 +00:00
parent 92835cf0d7
commit c6e04a5057
5 changed files with 139 additions and 96 deletions

View File

@ -1,2 +1,27 @@
# giambio
Giambio: Asynchronous Python made easy (and friendly)
# What's that?
giambio is a Python framework that implements the aysnchronous mechanism and allow to perform I/O multiplexing and basically do more than one thing at once.
This library implements what is known as a stackless mode of execution, or "green threads", though the latter term is misleading as **no multithreading is involved**.
## Disclaimer
Right now this is no more than a toy implementation to help me understand how Python and async work, but it will (hopefully) become a production ready library soon
## Let's Code!
Libraries like giambio shine the most when it comes to performing asyncronous I/O (reading a socket, writing to a file, stuff like that).
The most common example of this is a network server, which just can't be handle one client at a time only.
One possible approach to achieve concurrency is to use threads, and despite their bad reputation in Python due to the GIL, they actually might be a good choice when it comes to I/O (it is performed out of control of the GIL so that isn't trotthled like CPU-intensive tasks). Problem is, if you have 10000 concurrent connections, you would need to set a limit to the number of threads that can be spawned (you definitely don't want to spawn 10 thousands threads do you?). On top of that, threads are known to be tricky when it comes to synchronization and coordination, so in general you'll hear anyone yelling at you "Don't use threads!". That's when libraries like giambio come in handy!
A library like giambio implements some low-level primitives and a main loop (known as an **Event Loop**) that acts like a kernel: If a function needs a "system" call (in this case it's I/O) it will trigger a trap and stop, waiting for the event loop to return control and proceed. In the meantime, the loop catches the trap and uses its associated metadata to perform the requested operation. But since I/O 99% of the times implies waiting for a resource to become available, the loop will also run other tasks while it's waiting for that I/O to happen.
And that's how it works!

View File

@ -1,6 +1,8 @@
__author__ = "Nocturn9x aka Isgiambyy"
__version__ = (0, 0, 1)
from .core import EventLoop, sleep
from .core import EventLoop
from .exceptions import GiambioError, AlreadyJoinedError, CancelledError
from .traps import sleep, join
__all__ = ["EventLoop", "sleep", "GiambioError", "AlreadyJoinedError", "CancelledError"]
__all__ = ["EventLoop", "sleep", "join", "GiambioError", "AlreadyJoinedError", "CancelledError"]

51
giambio/abstractions.py Normal file
View File

@ -0,0 +1,51 @@
import types
class Result:
"""A wrapper for results of coroutines"""
def __init__(self, val=None, exc: Exception = None):
self.val = val
self.exc = exc
def __repr__(self):
return f"giambio.core.Result({self.val}, {self.exc})"
class Task:
"""A simple wrapper around a coroutine object"""
def __init__(self, coroutine: types.coroutine, loop):
self.coroutine = coroutine
self.status = False # Not ran yet
self.joined = False
self.result = None # Updated when the coroutine execution ends
self.loop = loop # The EventLoop object that spawned the task
self.cancelled = False
def run(self):
self.status = True
return self.coroutine.send(None)
def __repr__(self):
return f"giambio.core.Task({self.coroutine}, {self.status}, {self.joined}, {self.result})"
def throw(self, exception: Exception):
self.result = Result(None, exception)
return self.coroutine.throw(exception)
async def cancel(self):
return await cancel(self)
async def join(self):
return await join(self)
def get_result(self):
if self.result:
if self.result.exc:
raise self.result.exc
else:
return self.result.val

View File

@ -7,6 +7,8 @@ from .exceptions import AlreadyJoinedError, CancelledError
from timeit import default_timer
from time import sleep as wait
from .socket import AsyncSocket
from .traps import join, sleep, want_read, want_write, cancel
from .abstractions import Task, Result
class EventLoop:
@ -114,6 +116,8 @@ class EventLoop:
await want_read(sock)
return sock.accept()
async def sock_sendall(self, sock: socket.socket, data: bytes):
"""Sends all the passed data, as bytes, trough the socket asynchronously"""
@ -153,99 +157,9 @@ class EventLoop:
async def connect_sock(self, sock: socket.socket, addr: tuple):
await want_write(sock)
return sock.connect(addr)
try:
sock.connect(addr)
except BlockingIOError:
await want_write(sock)
class Result:
"""A wrapper for results of coroutines"""
def __init__(self, val=None, exc: Exception = None):
self.val = val
self.exc = exc
def __repr__(self):
return f"giambio.core.Result({self.val}, {self.exc})"
class Task:
"""A simple wrapper around a coroutine object"""
def __init__(self, coroutine: types.coroutine, loop: EventLoop):
self.coroutine = coroutine
self.status = False # Not ran yet
self.joined = False
self.result = None # Updated when the coroutine execution ends
self.loop = loop # The EventLoop object that spawned the task
self.cancelled = False
def run(self):
self.status = True
return self.coroutine.send(None)
def __repr__(self):
return f"giambio.core.Task({self.coroutine}, {self.status}, {self.joined}, {self.result})"
def throw(self, exception: Exception):
self.result = Result(None, exception)
return self.coroutine.throw(exception)
async def cancel(self):
return await cancel(self)
async def join(self):
return await join(self)
def get_result(self):
if self.result:
if self.result.exc:
raise self.result.exc
else:
return self.result.val
@types.coroutine
def sleep(seconds: int):
"""Pause the execution of a coroutine for the passed amount of seconds,
without blocking the entire event loop, which keeps watching for other events
This function is also useful as a sort of checkpoint, because it returns the execution
control to the scheduler, which can then switch to another task. If a coroutine does not have
enough calls to async methods (or 'checkpoints'), e.g one that needs the 'await' keyword before it, this might
affect performance as it would prevent the scheduler from switching tasks properly. If you feel
like this happens in your code, try adding a call to giambio.sleep(0); this will act as a checkpoint without
actually pausing the execution of your coroutine"""
yield "want_sleep", seconds
@types.coroutine
def want_read(sock: socket.socket):
"""'Tells' the event loop that there is some coroutine that wants to read from the passed socket"""
yield "want_read", sock
@types.coroutine
def want_write(sock: socket.socket):
"""'Tells' the event loop that there is some coroutine that wants to write into the passed socket"""
yield "want_write", sock
@types.coroutine
def join(task: Task):
"""'Tells' the scheduler that the desired task MUST be awaited for completion"""
if not task.cancelled:
task.joined = True
yield "want_join", task
return task.get_result()
@types.coroutine
def cancel(task: Task):
"""'Tells' the scheduler that the passed task must be cancelled"""
yield "want_cancel", task

51
giambio/traps.py Normal file
View File

@ -0,0 +1,51 @@
"""Helper methods to interact with the event loop"""
import types
import socket
from .abstractions import Task
@types.coroutine
def sleep(seconds: int):
"""Pause the execution of a coroutine for the passed amount of seconds,
without blocking the entire event loop, which keeps watching for other events
This function is also useful as a sort of checkpoint, because it returns the execution
control to the scheduler, which can then switch to another task. If a coroutine does not have
enough calls to async methods (or 'checkpoints'), e.g one that needs the 'await' keyword before it, this might
affect performance as it would prevent the scheduler from switching tasks properly. If you feel
like this happens in your code, try adding a call to giambio.sleep(0); this will act as a checkpoint without
actually pausing the execution of your coroutine"""
yield "want_sleep", seconds
@types.coroutine
def want_read(sock: socket.socket):
"""'Tells' the event loop that there is some coroutine that wants to read from the passed socket"""
yield "want_read", sock
@types.coroutine
def want_write(sock: socket.socket):
"""'Tells' the event loop that there is some coroutine that wants to write into the passed socket"""
yield "want_write", sock
@types.coroutine
def join(task: Task):
"""'Tells' the scheduler that the desired task MUST be awaited for completion"""
task.joined = True
yield "want_join", task
return task.get_result() # This raises an exception if the child task errored
@types.coroutine
def cancel(task: Task):
"""'Tells' the scheduler that the passed task must be cancelled"""
yield "want_cancel", task