The Main Loop

2019

Promises and "await"

Nikt wam nie da tyle, ile ja wam obiecam

- Admirał Jachaś

Promises, promises...

class Promise:
    def __init__(self):
        self.callback = None

    def then(self, callback):
        self.callback = callback

    def fulfill(self, result):
        if self.callback is not None:
            self.callback(result)

Promises, promises...

class FileProcessor:
    def __init__(self, input):
        self.input = input

    def read(self, size):
        p = Promise()
        # magic
        return p

    def process(self, data):
        p = Promise()
        # more magic!
        return p

    def start(self):
        self.read(1024).then(process)

Promises, promises...

class ReadPromise(Promise):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        handler = ReadHandler(self, self.descriptor, self.size)
        main_loop.add(handler)
class ReadHandler:
    def __init__(self, promise, descriptor, size):
        self.promise = promise
        self.descriptor = descriptor
        self.size = size
        self.data = b''

    def should_read(self):
        return True
        
    def handle_read(self):
        data = self.descriptor.read(self.size)
        self.data += data
        self.size -= len(data)
        if self.size == 0:
            self.promise.fulfill(self.data)

Promises, promises...

class FileProcessor:
    def __init__(self, input, output):
        self.input = inpit
        self.output = output

    def read(self, size):
        return ReadPromise(self.input, size)     

    def process(self, data):
        return IdlePromise(lambda: frobnicate(data))

    def write(self, data):
        return WritePromise(self.output, data)

    def run(self):
        self.read(1024).then(
            lambda data: self.process(data).then(
                write
            )
        )

Promises, promises...

class ComplexProcessor:
    def run(self):
        self.read(1024).then(
            lambda data: self.frobnicate(data).then(
                lambda result: self.twiddle(result).then(
                    lambda result: self.fudge(result).then(
                        # ...    
                )
            )
        )

Promises, promises...

class Promise:
    def __init__(self):
        self.callback = None
        self.next = Promise()

    def then(self, callback):
        self.callback = callback
        return self.next

    def fulfill(self, result):
        if self.callback is not None:
            result = self.callback(result)

            if isinstance(result, Promise):
                result.then(self.next.fulfill)

Promises, promises...

class FileProcessor:
    # ...

    def run(self):
        self.read(1024).then(process).then(write)
class ComplexProcessor:
    def run(self):
        self.read(1024)            \
            .then(self.frobnicate) \
            .then(self.twiddle)    \
            .then(self.fudge)      \
            .then(...)

Promises, promises...

read

main loop

process

write

a promise chain

There must be a better way

def complex_processor():
    yield 1
    yield 2
    yield 3 
    # ...
>>> g = complex_processor()
>>> g
<generator object complex_processor at 0x7f8900ee7db0>
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3

Functions, interrupted

def complex_processor():
    yield read()
    yield frobnicate()
    yield twiddle()
    yield fudge()
>>> processor = complex_processor()
>>> next(processor)
<ReadPromise object at 0x7f13992e9128>
>>> next(processor)
<FrobnicatePromise object at 0x7f13992e90f0>
>>> next(processor)
<TwiddlePromise object at 0x7f13992e9128>
>>> next(processor)
<FudgePromise object at 0x7f13992e90f0>

Functions, interrupted

def complex_processor():
    result = yield read(1024)
    frobnicated = yield frobnicate(result)
    twiddled = yield twiddle(frobnicated)
    fudged = yield fudge(twiddled)
    # ...

g = complex_processor()
reader = send(g, None)  # next(g)
frobnicator = send(g, b'some data')
twiddler = send(g, 'some frobnicated data')
fudger = send(g, 'some frobnicated data, but also twiddled')
# ...

Functions, continued

def complex_processor():
    result = yield read(1024)
    frobnicated = yield frobnicate(result)
    twiddled = yield twiddle(frobnicated)
    fudged = yield fudge(twiddled)
    # ...


def step(g, result=None):
    send(g, result).then(lambda result: step(g, result))

  
g = complex_processor()
step(g)

main_loop.run()

Functions, looped

Functions, coroutines

read

main loop

process

write

a promise chain

COROUTINE

def read(size):
    return file.read(size)


def complex_processor():
    result = read(1024)
    # ...

Composition: functions

def one_two():
    yield 1
    yield 2


def one_two_three_four():
    yield one_two()
    yield 3
    yield 4

Composition: generators

>>> g = one_two_three_four()
>>> next(g)
<generator object one_two at 0x7f13992dfc50>
>>> next(g)
3
>>> next(g)
4
def one_two():
    yield 1
    yield 2


def one_two_three_four():
    yield from one_two()
    yield 3
    yield 4

Composition: generators

>>> g = one_two_three_four()
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
4

PEP 380

def read(size):
    result = yield file.read(size)
    return result


def frobnicate(data):
    result = b''
    for i in data:
        result += yield frobnicate_byte(i)
    return result
    

def complex_processor():
    result = yield from read(1024)
    frobnicated = \
        yield from frobnicate(result)
    # ...

Composition: coroutines

C, interrupted

struct generator {
    void *next;
};

#define YIELD(g, v) do { \
    g->next = &&next##__LINE__; return v; next##__LINE__:; \
} while(0)


int generator(struct generator *g)
{
    if (g->next)
        goto *g->next;

    YIELD(g, 1);
    YIELD(g, 2);
}

struct generator g = { .next = NULL };
int one = generator(g);
int two = generator(g);
    
    

ProtoThreads

mrzechonek/ghetto-asyncio

Thank you

Questions?

Main Loop vol 3, await @ Silvair

By Michał Lowas-Rzechonek

Main Loop vol 3, await @ Silvair

Asynchronous programming in an embedded environment

  • 651