Embryoyo
Embryoyo

Reputation: 1

Python - Why does my timeout exception always execute?

I am attempting to grasp Python's asyncio library, and I'm having a problem with it's timeout exception. I can't figure out why does the "asyncio.TimeoutError" exception in the "async def create" function always execute at the end of my program even though the timeout limit hasn't passed. I would appreciate Your expert advise and opinion :)

Thank you for your time.

import asyncio
import multiprocessing as mp
from enum import Enum

class Sensor(Enum):
    GREEN = 0
    RED = 1

class State(Enum):
    NORMAL = 0
    MEDIUM = 1
    BURNED = 2

class Toaster:
    def __init__(self, min = 20, max = 50, temp = 0, timer = 0, state = State.NORMAL, sensor = Sensor.GREEN):
        self.min = min
        self.max = max
        self.temp = self.min
        self.timer = timer
        self.state = state
        self.sensor = sensor

    def display(self):
        print("\nTimer state:", self.timer, "seconds")
        print("Toast state:", self.state.name)
        print("Sensor state:", self.sensor.name)

    async def start(self):
        while True:
            if self.temp <= self.max:
                await asyncio.sleep(0.1)
                print("Temperature:", self.temp)
                self.temp+=1
            else:
                print("\nMaximum temperature", self.max, "celsius reached")
                await self.measure_state()
                await self.restart()
                break

    async def restart(self):
        while True:
            if self.temp >= self.min:
                await asyncio.sleep(0.1)
                print("Temperature:", self.temp)
                self.temp-=1
            else:
                self.sensor = Sensor.GREEN
                print("\nMinimum temperature", self.min, "celsius reached")
                break

    async def validateInput(self, message):
        valid = False
        while not valid:
            try:
                userInput = int(input(message))
                if userInput == 0 or userInput == 1:
                    valid = True
                    return userInput
                else:
                    raise ValueError("\nInvalid value", userInput)
            except ValueError as v:
                print(v)

    async def eject(self):
        self.display()
        message = "\nEject toast - 1(Yes), 0(No):"
        try:
            return await asyncio.wait_for(self.validateInput(message), timeout=1000)
        except asyncio.TimeoutError:
            print("Took too long - eject")
    async def repeat(self):
        message = "\nInject another toast - 1(Yes), 0(No):"
        try:
            return await asyncio.wait_for(self.validateInput(message), timeout=1000)
        except asyncio.TimeoutError:
            print("Took too long - repeat")
    async def measure_state(self):
        while True:
            await asyncio.sleep(5)
            self.timer+=50
            if self.timer == 50:
                print("\nToast is in it's", self.state.name, "state")
                if await self.eject() == 1:
                    print("\nToast ejected")
                    if await self.repeat() == 1:
                        self.timer = 0
                        self.state = State.NORMAL
                        await self.measure_state()
                    break
            elif self.timer == 100:
                self.state = State.MEDIUM
                self.sensor = Sensor.RED
                print("\nToast is in it's", self.state.name, "state")
                if await self.eject() == 1:
                    print("\nToast ejected")
                    if await self.repeat() == 1:
                        self.timer = 0
                        self.state = State.NORMAL
                        await self.measure_state()
                    break
            elif self.timer >= 150:
                self.state = State.BURNED
                print("\nToast is in it's", self.state.name, "state, ejecting toast")
                break

    async def toaster(self):
        message = "\nInsert a toast - 1(Yes), 0(No):"
        while await self.validateInput(message) != 1:
            print("\nPlease insert a toast")
        print("\nToast inserted")
        await self.start()

    async def create(self):
        x = loop.create_task(Toaster().toaster())
        y = loop.create_task(Toaster().toaster())
        z = loop.create_task(Toaster().toaster())
        try:
            await asyncio.wait([x, y, z], timeout=1000)
            raise asyncio.TimeoutError("\nTook too long - create")
        except asyncio.TimeoutError as t:
            print(t)
            x.cancel(), y.cancel(), z.cancel()

def get_process_count():
    nproc = mp.cpu_count()
    pool = mp.Pool(processes=nproc)
    return pool

class Connector(Toaster):
    pass

async def main():
       connector = Connector()
       result = get_process_count()
       result.map(await connector.create())
       await asyncio.gather(result)

if __name__ == "__main__":
    loop = None
    try:
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())
    except Exception as e:
        pass
    finally:
        loop.close()

Upvotes: 0

Views: 320

Answers (1)

wwii
wwii

Reputation: 23753

In create() you raise the exception immediately after waiting for tasks x,y,z to complete. Adding some prints to toaster and create shows that the three tasks finish so execution just resumes with the raise asyncio.TimeoutError... statement.

...
    async def toaster(self):
        message = "\nInsert a toast - 1(Yes), 0(No):"
        while await self.validateInput(message) != 1:
            print("\nPlease insert a toast")
        print("\nToast inserted")
        await self.start()
        return 'FINISHED'

    async def create(self):
        x = loop.create_task(Toaster().toaster())
        y = loop.create_task(Toaster().toaster())
        z = loop.create_task(Toaster().toaster())
        try:
            await asyncio.wait([x, y, z], timeout=1000)
            for thing in (x,y,z):
                print(thing)
            raise asyncio.TimeoutError("\nTook too long - create")    # <-- you raise the exception Here!
        except asyncio.TimeoutError as t:
            print(t)
            x.cancel(), y.cancel(), z.cancel()

Results in

>>>
...
...
Temperature: 20

Minimum temperature 20 celsius reached
Temperature: 20

Minimum temperature 20 celsius reached
Temperature: 20

Minimum temperature 20 celsius reached
<Task finished coro=<Toaster.toaster() done, defined at tmp.py:129> result='FINISHED'>
<Task finished coro=<Toaster.toaster() done, defined at tmp.py:129> result='FINISHED'>
<Task finished coro=<Toaster.toaster() done, defined at tmp.py:129> result='FINISHED'>

Took too long - create

  • I inserted 3 toasts and ejected them at first request.
  • I went through a dozen cycles of injecting and ejecting toast and it didn't timeout but the exception was raised as soon as a declined to inject more toast.

...why does the "asyncio.TimeoutError" exception in the "async def create" function always execute at the end of my program ...?
I would say you wrote it to do that - looks like that was your intention.

Upvotes: 1

Related Questions