outsource mainloop logic, to make it easy to use it with asyncio