{"action":"opened","issue":{"url":"https://api.github.com/repos/dabeaz/curio/issues/96","repository_url":"https://api.github.com/repos/dabeaz/curio","labels_url":"https://api.github.com/repos/dabeaz/curio/issues/96/labels{/name}","comments_url":"https://api.github.com/repos/dabeaz/curio/issues/96/comments","events_url":"https://api.github.com/repos/dabeaz/curio/issues/96/events","html_url":"https://github.com/dabeaz/curio/issues/96","id":188146073,"number":96,"title":"Need a way to protect a region from cancellation (like \"masking interrupts\")","user":{"login":"njsmith","id":609896,"avatar_url":"https://avatars.githubusercontent.com/u/609896?v=3","gravatar_id":"","url":"https://api.github.com/users/njsmith","html_url":"https://github.com/njsmith","followers_url":"https://api.github.com/users/njsmith/followers","following_url":"https://api.github.com/users/njsmith/following{/other_user}","gists_url":"https://api.github.com/users/njsmith/gists{/gist_id}","starred_url":"https://api.github.com/users/njsmith/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/njsmith/subscriptions","organizations_url":"https://api.github.com/users/njsmith/orgs","repos_url":"https://api.github.com/users/njsmith/repos","events_url":"https://api.github.com/users/njsmith/events{/privacy}","received_events_url":"https://api.github.com/users/njsmith/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2016-11-09T01:09:28Z","updated_at":"2016-11-09T01:09:28Z","closed_at":null,"body":"Here's an example of a simple bidirectional proxy using curio: https://gist.github.com/njsmith/4b4e75a9ab9e975705412f8f8e866948\r\n\r\nThe core idea is that we spawn two tasks to handle the copying in each direction:\r\n```python\r\nasync def copy_all_bidir(sock1, sock2):\r\n    await all_or_none([\r\n        await curio.spawn(copy_all(sock1, sock2)),\r\n        await curio.spawn(copy_all(sock2, sock1)),\r\n])\r\n```\r\n\r\nThe tricky part is managing these tasks. Basically with regards to exceptions/timeouts/cancellation/etc., we want it to act like the two `copy_all` tasks are both \"part of\" the `copy_all_bidir` task. So:\r\n* If one of the tasks raises an exception, the other task should immediately be canceled, and the exception should propagate out of `copy_all_bidir`\r\n* If both of the tasks complete, then `copy_all_bidir` should complete successfully\r\n* If `copy_all_bidir` is cancelled, then both tasks should be canceled as part of `copy_all_bidir`'s cleanup\r\n\r\nThe `all_or_none` helper there tries to do this, but AFAICT it can't quite achieve the last requirement given curio's current APIs. The problem sequence is:\r\n\r\n1. One of the tasks raises an exception\r\n2. `all_or_none` catches this exception, and starts to cancel the other task\r\n3. `all_or_none` itself gets canceled, thus canceling the cancellation and making everyone's head explode\r\n\r\nMy suggestion for what it should look like is:\r\n```python\r\nasync def all_or_none(tasks):\r\n    try:\r\n        async for task in curio.wait(tasks):\r\n            await task.join()\r\n    except:\r\n        async with curio.defer_cancellation:\r\n            for task in tasks:\r\n                await task.cancel()\r\n        raise\r\n```\r\nexcept that `curio.defer_cancellation` isn't a thing that exists right now.\r\n\r\nSo this issue is to propose that `curio.defer_cancellation` be a thing that exists.\r\n\r\n(Guess how I'm distracting myself from election news!)"}}
{"actor":{"display_login":"njsmith"}}