# -*- Mode: text -*-

TO DO LARGER TASKS

- Need more examples.

- Benchmarkable but more realistic HTTP server?

- Example of using UDP.

- Write up a tutorial for the scheduling API.

- More systematic approach to logging.  Logger objects?  What about
  heavy-duty logging, tracking essentially all task state changes?

- Restructure directory, move demos and benchmarks to subdirectories.


TO DO LATER

- When multiple tasks are accessing the same socket, they should
  either get interleaved I/O or an immediate exception; it should not
  compromise the integrity of the scheduler or the app or leave a task
  hanging.

- For epoll you probably want to check/(log?) EPOLLHUP and EPOLLERR errors.

- Add the simplest API possible to run a generator with a timeout.

- Ensure multiple tasks can do atomic writes to the same pipe (since
  UNIX guarantees that short writes to pipes are atomic).

- Ensure some easy way of distributing accepted connections across tasks.

- Be wary of thread-local storage.  There should be a standard API to
  get the current Context (which holds current task, event loop, and
  maybe more) and a standard meta-API to change how that standard API
  works (i.e. without monkey-patching).

- See how much of asyncore I've already replaced.

- Could BufferedReader reuse the standard io module's readers???

- Support ZeroMQ "sockets" which are user objects.  Though possibly
  this can be supported by getting the underlying fd?  See
  http://mail.python.org/pipermail/python-ideas/2012-October/017532.html
  OTOH see 
  https://github.com/zeromq/pyzmq/blob/master/zmq/eventloop/ioloop.py

- Study goroutines (again).

- Benchmarks: http://nichol.as/benchmark-of-python-web-servers


FROM OLDER LIST

- Multiple readers/writers per socket?  (At which level? pollster,
  eventloop, or scheduler?)

- Could poll() usefully be an iterator?

- Do we need to support more epoll and/or kqueue modes/flags/options/etc.?

- Optimize register/unregister calls away if they cancel each other out?

- Add explicit wait queue to wait for Task's completion, instead of
  callbacks?

- Look at pyfdpdlib's ioloop.py:
  http://code.google.com/p/pyftpdlib/source/browse/trunk/pyftpdlib/lib/ioloop.py


MISTAKES I MADE

- Forgetting yield from.  (E.g.: scheduler.sleep(1); listener.accept().)

- Forgot to add bare yield at end of internal function, after block().

- Forgot to call add_done_callback().

- Forgot to pass an undoer to block(), bug only found when cancelled.

- Subtle accounting mistake in a callback.

- Used context.eventloop from a different thread, forgetting about TLS.

- Nasty race: eventloop.ready may contain both an I/O callback and a
  cancel callback.  How to avoid?  Keep the DelayedCall in ready.  Is
  that enough?

- If a toplevel task raises an error it just stops and nothing is logged
  unless you have debug logging on.  This confused me.  (Then again,
  previously I logged whenever a task raised an error, and that was too
  chatty...)

- Forgot to set the connection socket returned by accept() in
  nonblocking mode.

- Nastiest so far (cost me about a day): A race condition in
  call_in_thread() where the Future's done_callback (which was
  task.unblock()) would run immediately at the time when
  add_done_callback() was called, and this screwed over the task
  state.  Solution: wrap the callback in eventloop.call_later().
  Ironically, I had a comment stating there might be a race condition.

- Another bug where I was calling unblock() for the current thread
  immediately after calling block(), before yielding.

- readexactly() wasn't checking for EOF, so could be looping.
  (Worse, the first fix I attempted was wrong.)

- Spent a day trying to understand why a tentative patch trying to
  move the recv() implementation into the eventloop (or the pollster)
  resulted in problems cancelling a recv() call.  Ultimately the
  problem is that the cancellation mechanism is part of the coroutine
  scheduler, which simply throws an exception into a task when it next
  runs, and there isn't anything to be interrupted in the eventloop;
  but the eventloop still has a reader registered (which will never
  fire because I suspended the server -- that's my test case :-).
  Then, the eventloop keeps running until the last file descriptor is
  unregistered.  What contributed to this disaster?
  * I didn't build the whole infrastructure, just played with recv()
  * I don't have unittests
  * I don't have good logging to see what is going

- In sockets.py, in some SSL error handling code, used the wrong
  variable (sock instead of sslsock).  A linter would have found this.

- In polling.py, in KqueuePollster.register_writer(), a copy/paste
  error where I was testing for "if fd not in self.readers" instead of
  writers.  This only came out when I had both a reader and a writer
  for the same fd.

- Submitted some changes prematurely (forgot to pass the filename on
  hg ci).

- Forgot again that shutdown(SHUT_WR) on an ssl socket does not work
  as I expected.  I ran into this with the origininal sockets.py and
  again in transport.py.

- Having the same callback for both reading and writing has a problem:
  it may be scheduled twice, and if the first call closes the socket,
  the second runs into trouble.


MISTAKES I MADE IN TULIP V2

- Nice one: Future._schedule_callbacks() wasn't scheduling any callbacks.
  Spot the bug in these four lines:

    def _schedule_callbacks(self):
      callbacks = self._callbacks[:]
      self._callbacks[:] = []
      for callback in self._callbacks:
          self._event_loop.call_soon(callback, self)

  The good news is that I found it with a unittest (albeit not the
  unittest intended to exercise this particular method :-( ).

- In _make_self_pipe_or_sock(), called _pollster.register_reader()
  instead of add_reader(), trying to optimize something but breaking
  things instead (since the -- internal -- API of register_reader()
  had changed).
