This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Please add async write method to asyncio.StreamWriter
Type: Stage:
Components: asyncio Versions: Python 3.5
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: gvanrossum, martin.panter, pfalcon, vstinner, yselivanov
Priority: normal Keywords:

Created on 2015-06-14 07:50 by pfalcon, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (6)
msg245336 - (view) Author: Paul Sokolovsky (pfalcon) * Date: 2015-06-14 07:50
This issue was brought is somewhat sporadic manner on python-tulip mailing list, hence this ticket. The discussion on the ML:

https://groups.google.com/d/msg/python-tulip/JA0-FC_pliA/knMvVGxp2WsJ
(all other messages below threaded from this)

https://groups.google.com/d/msg/python-tulip/JA0-FC_pliA/lGqT54yupOIJ
https://groups.google.com/d/msg/python-tulip/JA0-FC_pliA/U0NBC1jLGSgJ
https://groups.google.com/d/msg/python-tulip/JA0-FC_pliA/zIx59jj8krsJ
https://groups.google.com/d/msg/python-tulip/JA0-FC_pliA/zSpjGKv23ioJ
https://groups.google.com/d/msg/python-tulip/JA0-FC_pliA/3mfGI8HIe_gJ
https://groups.google.com/d/msg/python-tulip/JA0-FC_pliA/rM4fyA9qlY4J

Summary of arguments:

1. This would make such async_write() (a tentative name) symmetrical in usage with read() method (i.e. be a coroutine to be used with "yield from"/"await"), which certainly reduce user confusion and will help novices to learn/use asyncio.

2. write() method is described (by transitively referring to WriteTransport.write()) as "This method does not block; it buffers the data and arranges for it to be sent out asynchronously." Such description implies requirement of unlimited data buffering. E.g., being fed with 1TB of data, it still must buffer it. Bufferings of such size can't/won't work in practice - they only will lead to excessive swapping and/or termination due to out of memory conditions. Thus, providing only synchronous high-level write operation goes against basic system reliability/security principles.

3. The whole concept of synchronous write in an asynchronous I/O framework stems from: 1) the way it was done in some pre-existing Python async I/O frameworks ("pre-existing" means brought up with older versions of Python and based on concepts available at that time; many people use word "legacy" in such contexts); 2) on PEP3153, which essentially captures ideas used in the aforementioned pre-existing Python frameworks. PEP3153 was rejected; it also contains some "interesting" claims like "Considered API alternatives - Generators as producers - [...] - nobody produced actually working code demonstrating how they could be used." That wasn't true at the time of PEP writing (http://www.dabeaz.com/generators/ , 2008, 2009), and asyncio is actually *the* framework which uses generators as producers.

asyncio also made a very honorable step of uniting generators/coroutine and Transport paradigm - note that as PEP3153 shows, Transport proponents contrasted it with coroutine-based design. But asyncio also blocked (in both senses) high-level I/O on Transport paradigm. What I'm arguing is not that Transports are good or bad, but that there should be a way to consistently use coroutine paradigm for I/O in asyncio - for people who may appreciate it. This will also enable alternative implementations of asyncio subsets without Transport layer, with less code size, and thus more suitable for constrained environments.

Proposed change is to add following to asyncio.StreamWriter implementation:

@coroutine
def async_write(self, data):
    self.write(data)

I.e. default implementation will be just coroutine version of synchronous write() method. The messages linked above discuss alternative implementations (which are really interesting for complete alternative implementations of asyncio).


The above changes are implemented in MicroPython's uasyncio package, which asyncio subset for memory-constrained systems.

Thanks for your consideration!
msg245337 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2015-06-14 09:39
Paul, you have brought this up many times before, and you have been refuted each time. Reading and writing just aren't symmetric operations. If you need awrite(), it's a two-line helper function you can easily write yourself.
msg245344 - (view) Author: Paul Sokolovsky (pfalcon) * Date: 2015-06-14 16:26
No, I haven't brought this "many times before". Discussion on the mailing list last week was first time I brought up *this* issue. But it's indeed not the first time I provide feedback regarding various aspects of asyncio, so I wonder if this issue was closed because of "this" in "this issue" or because of "many times before"...

> If you need awrite(), it's a two-line helper function you can easily write yourself.

Yes, and I have it, but I don't see how that helps anybody else. If it's 2-liner, why don't you want to add it? Can there please be discussion of the arguments provided in the description?
msg245349 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2015-06-14 18:47
Most people actually are better off with just write(), and an API with more choices is not necessarily better. I'm sure this has come up before, I could've sworn it was you, sorry if it wasn't.

Here's one reason why I don't like your proposed API. The read() and write() calls *aren't* symmetric: When you call read(), you are interested in the return value. If you forget the "yield from" (or 'async') your use of the return value will most likely raise an exception immediately, so you will notice (and pinpoint) the bug in your code instantly. But with write() there is no natural return value, so if write() was a coroutine, the common mistake (amongst beginners) of forgetting the "yield from" or 'async' would be much harder to debug -- your program happily proceeds, but now it's likely hung, waiting for a response that it won't get (because the other side didn't get what you meant to write) or perhaps you get a cryptic error (because the other side saw the thing you wrote next).
msg245373 - (view) Author: Paul Sokolovsky (pfalcon) * Date: 2015-06-15 10:36
Thanks for the response.

> and an API with more choices is not necessarily better.

I certainly agree, and that's why I try to implement MicroPython's uasyncio solely in terms of coroutines, without Futures and Transports. But I of course can't argue for dropping Transports in asyncio, so the only argument I'm left with is consistency of API (letting use coroutines everywhere).

> I'm sure this has come up before, I could've sworn it was you, sorry if it wasn't.

No, I brought issue of Futures dependency (yes, that was wide and long discussion), then about yielding a coroutine instance as a way to schedule it. But during the time I'm on python-tulip, I didn't remember someone bringing up issue of asymmetry between read & write, but I would imagine someone would have done that before me. (Which might hint that the issue exists ;-) ).

> the common mistake (amongst beginners) of forgetting the "yield from" or 'async' would be much harder to debug

So, this is not first time you provide this argument for different cases, one would think that this pin-points pretty serious flaw in the language and it's priority to find a solution for it, and yet PEP3152 which does exactly that was rejected, so maybe it's not that *serious*. Indeed, it's common sense that it's possible to make a hard to debug mistake in any program, and in any concurrent program (doesn't matter of which paradigm) it's order of magnitude easier to do one and harder to debug respectively. But asyncio does have tools to debug such issue. And one would think that easiest way to preclude mistake is to avoid inconsistencies in the API.

I know there's a very find balance between all the arguments, and only you can know where it lies, but kindly accept external feedback that the above explanation (syntax is more prone to mistakes) looks like universal objectivized rejection in rather subjective cases.

What saddens me here is that this decision puts pretty high lower bound for asyncio memory usage (and I tried hard to prove that asyncio is suitable paradigm even for smaller, IoT-class devices). It's also hard to argue that Python isn't worse than Go, Rust and other new kids on the block - because indeed, how one can argue that, if even the language author uses argument "language syntax, while exists, isn't good enough to do the obvious things".
msg245403 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2015-06-15 19:36
Good pontificating, Paul.
History
Date User Action Args
2022-04-11 14:58:18adminsetgithub: 68637
2015-06-27 04:30:58martin.pantersetnosy: + martin.panter
2015-06-15 19:36:08gvanrossumsetmessages: + msg245403
2015-06-15 10:36:05pfalconsetmessages: + msg245373
2015-06-14 18:47:42gvanrossumsetmessages: + msg245349
2015-06-14 16:26:36pfalconsetmessages: + msg245344
2015-06-14 09:39:41gvanrossumsetstatus: open -> closed
resolution: rejected
messages: + msg245337
2015-06-14 07:50:50pfalconcreate