asyncio + aiohttp: overlapping IO with sleeping
When all coroutines are waiting, asyncio listens for events to wake them up again. A common example would be asyncio.sleep()
, which registers a timed event. In practice an event is usually an IO socket ready for receiving or sending new data.
To get a better understanding of this behaviour, I set up a simple test: It sends an http request to localhost and waits for the response. On localhost, I've set up a flask server which waits for 1 second before responding. After sending the request, the client sleeps for 1 second, then it awaits the response. I would expect this to return in rougly a second, since both my program and the server should sleep in parallel. But it takes 2 seconds:
import aiohttp
import asyncio
from time import perf_counter
async def main():
async with aiohttp.ClientSession() as session:
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
loop = asyncio.get_event_loop()
start = perf_counter()
results = loop.run_until_complete(main())
stop = perf_counter()
print(f"took {stop-start} seconds") # 2.01909
What is asyncio doing here, why can't I overlap waiting times ?
I'm not interested in the specific scenario of HTTP requests, aiohttp is only used to construct an example. Which is probably a bit dangerous: This could be related to aiohttp and not asyncio at all.
Actually, I expect this to be the case (hence the question title about both asyncio and aiohttp).
My first intuition was that the request is maybe not sent before calling asyncio.sleep().
So I reordered things a bit:
# start coroutine
text = response.text()
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await text
But this still takes two seconds.
Ok, now to be really sure that the request was sent off before going to sleep, I added print("incoming")
to the route on the server, before it goes to sleep. I also changed the length of sleeping time to 10 seconds on the client. The server prints incoming immediately after the client is run. The client takes 11 seconds in total.
@app.route('/')
def index():
print("incoming")
time.sleep(1)
return 'done'
Since the HTTP request is made immediately, the server has definitely sent off an answer before the client wakes up from asyncio.sleep().
It seems to me that the socket providing the HTTP request should be ready as soon as the client wakes up. But still, the total runtime is always an addition of client and server waiting times.
Am I misusing asyncio somehow, or is this related to aiohttp after all ?
python async-await python-asyncio aiohttp
add a comment |
When all coroutines are waiting, asyncio listens for events to wake them up again. A common example would be asyncio.sleep()
, which registers a timed event. In practice an event is usually an IO socket ready for receiving or sending new data.
To get a better understanding of this behaviour, I set up a simple test: It sends an http request to localhost and waits for the response. On localhost, I've set up a flask server which waits for 1 second before responding. After sending the request, the client sleeps for 1 second, then it awaits the response. I would expect this to return in rougly a second, since both my program and the server should sleep in parallel. But it takes 2 seconds:
import aiohttp
import asyncio
from time import perf_counter
async def main():
async with aiohttp.ClientSession() as session:
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
loop = asyncio.get_event_loop()
start = perf_counter()
results = loop.run_until_complete(main())
stop = perf_counter()
print(f"took {stop-start} seconds") # 2.01909
What is asyncio doing here, why can't I overlap waiting times ?
I'm not interested in the specific scenario of HTTP requests, aiohttp is only used to construct an example. Which is probably a bit dangerous: This could be related to aiohttp and not asyncio at all.
Actually, I expect this to be the case (hence the question title about both asyncio and aiohttp).
My first intuition was that the request is maybe not sent before calling asyncio.sleep().
So I reordered things a bit:
# start coroutine
text = response.text()
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await text
But this still takes two seconds.
Ok, now to be really sure that the request was sent off before going to sleep, I added print("incoming")
to the route on the server, before it goes to sleep. I also changed the length of sleeping time to 10 seconds on the client. The server prints incoming immediately after the client is run. The client takes 11 seconds in total.
@app.route('/')
def index():
print("incoming")
time.sleep(1)
return 'done'
Since the HTTP request is made immediately, the server has definitely sent off an answer before the client wakes up from asyncio.sleep().
It seems to me that the socket providing the HTTP request should be ready as soon as the client wakes up. But still, the total runtime is always an addition of client and server waiting times.
Am I misusing asyncio somehow, or is this related to aiohttp after all ?
python async-await python-asyncio aiohttp
add a comment |
When all coroutines are waiting, asyncio listens for events to wake them up again. A common example would be asyncio.sleep()
, which registers a timed event. In practice an event is usually an IO socket ready for receiving or sending new data.
To get a better understanding of this behaviour, I set up a simple test: It sends an http request to localhost and waits for the response. On localhost, I've set up a flask server which waits for 1 second before responding. After sending the request, the client sleeps for 1 second, then it awaits the response. I would expect this to return in rougly a second, since both my program and the server should sleep in parallel. But it takes 2 seconds:
import aiohttp
import asyncio
from time import perf_counter
async def main():
async with aiohttp.ClientSession() as session:
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
loop = asyncio.get_event_loop()
start = perf_counter()
results = loop.run_until_complete(main())
stop = perf_counter()
print(f"took {stop-start} seconds") # 2.01909
What is asyncio doing here, why can't I overlap waiting times ?
I'm not interested in the specific scenario of HTTP requests, aiohttp is only used to construct an example. Which is probably a bit dangerous: This could be related to aiohttp and not asyncio at all.
Actually, I expect this to be the case (hence the question title about both asyncio and aiohttp).
My first intuition was that the request is maybe not sent before calling asyncio.sleep().
So I reordered things a bit:
# start coroutine
text = response.text()
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await text
But this still takes two seconds.
Ok, now to be really sure that the request was sent off before going to sleep, I added print("incoming")
to the route on the server, before it goes to sleep. I also changed the length of sleeping time to 10 seconds on the client. The server prints incoming immediately after the client is run. The client takes 11 seconds in total.
@app.route('/')
def index():
print("incoming")
time.sleep(1)
return 'done'
Since the HTTP request is made immediately, the server has definitely sent off an answer before the client wakes up from asyncio.sleep().
It seems to me that the socket providing the HTTP request should be ready as soon as the client wakes up. But still, the total runtime is always an addition of client and server waiting times.
Am I misusing asyncio somehow, or is this related to aiohttp after all ?
python async-await python-asyncio aiohttp
When all coroutines are waiting, asyncio listens for events to wake them up again. A common example would be asyncio.sleep()
, which registers a timed event. In practice an event is usually an IO socket ready for receiving or sending new data.
To get a better understanding of this behaviour, I set up a simple test: It sends an http request to localhost and waits for the response. On localhost, I've set up a flask server which waits for 1 second before responding. After sending the request, the client sleeps for 1 second, then it awaits the response. I would expect this to return in rougly a second, since both my program and the server should sleep in parallel. But it takes 2 seconds:
import aiohttp
import asyncio
from time import perf_counter
async def main():
async with aiohttp.ClientSession() as session:
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
loop = asyncio.get_event_loop()
start = perf_counter()
results = loop.run_until_complete(main())
stop = perf_counter()
print(f"took {stop-start} seconds") # 2.01909
What is asyncio doing here, why can't I overlap waiting times ?
I'm not interested in the specific scenario of HTTP requests, aiohttp is only used to construct an example. Which is probably a bit dangerous: This could be related to aiohttp and not asyncio at all.
Actually, I expect this to be the case (hence the question title about both asyncio and aiohttp).
My first intuition was that the request is maybe not sent before calling asyncio.sleep().
So I reordered things a bit:
# start coroutine
text = response.text()
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await text
But this still takes two seconds.
Ok, now to be really sure that the request was sent off before going to sleep, I added print("incoming")
to the route on the server, before it goes to sleep. I also changed the length of sleeping time to 10 seconds on the client. The server prints incoming immediately after the client is run. The client takes 11 seconds in total.
@app.route('/')
def index():
print("incoming")
time.sleep(1)
return 'done'
Since the HTTP request is made immediately, the server has definitely sent off an answer before the client wakes up from asyncio.sleep().
It seems to me that the socket providing the HTTP request should be ready as soon as the client wakes up. But still, the total runtime is always an addition of client and server waiting times.
Am I misusing asyncio somehow, or is this related to aiohttp after all ?
python async-await python-asyncio aiohttp
python async-await python-asyncio aiohttp
asked Nov 13 '18 at 16:31
lhklhk
6,65585589
6,65585589
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
The problem is that one second happens in server is performed in async with session.get("http://127.0.0.1:5000/") as response:
.
The http request finishes before you get this response
object.
You can test it by:
...
async def main():
async with aiohttp.ClientSession() as session:
start = perf_counter()
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
end = perf_counter()
print(f"took {end-start} seconds to get response")
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
...
And btw you can surely overlap this waiting time, as long as you have another running coroutine.
oh that makes sense. But then I don't understand the design of aiohttp. If they already wait for a response in the "async with session.get", why do I have to wait for the response.text() again ? It's in the example on their server: aiohttp.readthedocs.io/en/stable
– lhk
Nov 13 '18 at 17:09
Btw, I already wondered why the example didn't have to wait for the response status. Your answer explains that :)
– lhk
Nov 13 '18 at 17:10
Yeah I cannot understand that somehow. I think there are two reasons. First,await res.text()
is reading content from a stream, actually the behavior depends on OS. I've heard that Windows supports real asynchronous data transporting, although I've never checked it by myself. So in this case, it makes sense. Another reason is to keep consistence: all perations should be coroutine.
– Sraw
Nov 13 '18 at 17:16
@lhksession.get()
gets the head of the response, which is enough to tell e.g. the status code and the characteristics of the response.response.text()
reads the body of the response, which can take additional time if the body is sufficiently long.
– user4815162342
Nov 13 '18 at 18:08
add a comment |
Your testing code has three awaits (two explicit and one hidden in async with
) in series, so you don't get any parallel waiting. The code that tests the scenario you describe is something along the lines of:
async def download():
async with aiohttp.ClientSession() as session:
async with session.get("http://127.0.0.1:5000/") as response:
text = await response.text()
return text
async def main():
loop = asyncio.get_event_loop()
# have download start "in the background"
dltask = loop.create_task(download())
# now sleep
await asyncio.sleep(1)
# and now await the end of the download
text = await dltask
Running this coroutine should take the expected time.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53285499%2fasyncio-aiohttp-overlapping-io-with-sleeping%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
The problem is that one second happens in server is performed in async with session.get("http://127.0.0.1:5000/") as response:
.
The http request finishes before you get this response
object.
You can test it by:
...
async def main():
async with aiohttp.ClientSession() as session:
start = perf_counter()
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
end = perf_counter()
print(f"took {end-start} seconds to get response")
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
...
And btw you can surely overlap this waiting time, as long as you have another running coroutine.
oh that makes sense. But then I don't understand the design of aiohttp. If they already wait for a response in the "async with session.get", why do I have to wait for the response.text() again ? It's in the example on their server: aiohttp.readthedocs.io/en/stable
– lhk
Nov 13 '18 at 17:09
Btw, I already wondered why the example didn't have to wait for the response status. Your answer explains that :)
– lhk
Nov 13 '18 at 17:10
Yeah I cannot understand that somehow. I think there are two reasons. First,await res.text()
is reading content from a stream, actually the behavior depends on OS. I've heard that Windows supports real asynchronous data transporting, although I've never checked it by myself. So in this case, it makes sense. Another reason is to keep consistence: all perations should be coroutine.
– Sraw
Nov 13 '18 at 17:16
@lhksession.get()
gets the head of the response, which is enough to tell e.g. the status code and the characteristics of the response.response.text()
reads the body of the response, which can take additional time if the body is sufficiently long.
– user4815162342
Nov 13 '18 at 18:08
add a comment |
The problem is that one second happens in server is performed in async with session.get("http://127.0.0.1:5000/") as response:
.
The http request finishes before you get this response
object.
You can test it by:
...
async def main():
async with aiohttp.ClientSession() as session:
start = perf_counter()
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
end = perf_counter()
print(f"took {end-start} seconds to get response")
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
...
And btw you can surely overlap this waiting time, as long as you have another running coroutine.
oh that makes sense. But then I don't understand the design of aiohttp. If they already wait for a response in the "async with session.get", why do I have to wait for the response.text() again ? It's in the example on their server: aiohttp.readthedocs.io/en/stable
– lhk
Nov 13 '18 at 17:09
Btw, I already wondered why the example didn't have to wait for the response status. Your answer explains that :)
– lhk
Nov 13 '18 at 17:10
Yeah I cannot understand that somehow. I think there are two reasons. First,await res.text()
is reading content from a stream, actually the behavior depends on OS. I've heard that Windows supports real asynchronous data transporting, although I've never checked it by myself. So in this case, it makes sense. Another reason is to keep consistence: all perations should be coroutine.
– Sraw
Nov 13 '18 at 17:16
@lhksession.get()
gets the head of the response, which is enough to tell e.g. the status code and the characteristics of the response.response.text()
reads the body of the response, which can take additional time if the body is sufficiently long.
– user4815162342
Nov 13 '18 at 18:08
add a comment |
The problem is that one second happens in server is performed in async with session.get("http://127.0.0.1:5000/") as response:
.
The http request finishes before you get this response
object.
You can test it by:
...
async def main():
async with aiohttp.ClientSession() as session:
start = perf_counter()
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
end = perf_counter()
print(f"took {end-start} seconds to get response")
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
...
And btw you can surely overlap this waiting time, as long as you have another running coroutine.
The problem is that one second happens in server is performed in async with session.get("http://127.0.0.1:5000/") as response:
.
The http request finishes before you get this response
object.
You can test it by:
...
async def main():
async with aiohttp.ClientSession() as session:
start = perf_counter()
# this http request will take 1 second to respond
async with session.get("http://127.0.0.1:5000/") as response:
end = perf_counter()
print(f"took {end-start} seconds to get response")
# yield control for 1 second
await asyncio.sleep(1)
# wait for the http request to return
text = await response.text()
return text
...
And btw you can surely overlap this waiting time, as long as you have another running coroutine.
answered Nov 13 '18 at 16:49
SrawSraw
7,48631438
7,48631438
oh that makes sense. But then I don't understand the design of aiohttp. If they already wait for a response in the "async with session.get", why do I have to wait for the response.text() again ? It's in the example on their server: aiohttp.readthedocs.io/en/stable
– lhk
Nov 13 '18 at 17:09
Btw, I already wondered why the example didn't have to wait for the response status. Your answer explains that :)
– lhk
Nov 13 '18 at 17:10
Yeah I cannot understand that somehow. I think there are two reasons. First,await res.text()
is reading content from a stream, actually the behavior depends on OS. I've heard that Windows supports real asynchronous data transporting, although I've never checked it by myself. So in this case, it makes sense. Another reason is to keep consistence: all perations should be coroutine.
– Sraw
Nov 13 '18 at 17:16
@lhksession.get()
gets the head of the response, which is enough to tell e.g. the status code and the characteristics of the response.response.text()
reads the body of the response, which can take additional time if the body is sufficiently long.
– user4815162342
Nov 13 '18 at 18:08
add a comment |
oh that makes sense. But then I don't understand the design of aiohttp. If they already wait for a response in the "async with session.get", why do I have to wait for the response.text() again ? It's in the example on their server: aiohttp.readthedocs.io/en/stable
– lhk
Nov 13 '18 at 17:09
Btw, I already wondered why the example didn't have to wait for the response status. Your answer explains that :)
– lhk
Nov 13 '18 at 17:10
Yeah I cannot understand that somehow. I think there are two reasons. First,await res.text()
is reading content from a stream, actually the behavior depends on OS. I've heard that Windows supports real asynchronous data transporting, although I've never checked it by myself. So in this case, it makes sense. Another reason is to keep consistence: all perations should be coroutine.
– Sraw
Nov 13 '18 at 17:16
@lhksession.get()
gets the head of the response, which is enough to tell e.g. the status code and the characteristics of the response.response.text()
reads the body of the response, which can take additional time if the body is sufficiently long.
– user4815162342
Nov 13 '18 at 18:08
oh that makes sense. But then I don't understand the design of aiohttp. If they already wait for a response in the "async with session.get", why do I have to wait for the response.text() again ? It's in the example on their server: aiohttp.readthedocs.io/en/stable
– lhk
Nov 13 '18 at 17:09
oh that makes sense. But then I don't understand the design of aiohttp. If they already wait for a response in the "async with session.get", why do I have to wait for the response.text() again ? It's in the example on their server: aiohttp.readthedocs.io/en/stable
– lhk
Nov 13 '18 at 17:09
Btw, I already wondered why the example didn't have to wait for the response status. Your answer explains that :)
– lhk
Nov 13 '18 at 17:10
Btw, I already wondered why the example didn't have to wait for the response status. Your answer explains that :)
– lhk
Nov 13 '18 at 17:10
Yeah I cannot understand that somehow. I think there are two reasons. First,
await res.text()
is reading content from a stream, actually the behavior depends on OS. I've heard that Windows supports real asynchronous data transporting, although I've never checked it by myself. So in this case, it makes sense. Another reason is to keep consistence: all perations should be coroutine.– Sraw
Nov 13 '18 at 17:16
Yeah I cannot understand that somehow. I think there are two reasons. First,
await res.text()
is reading content from a stream, actually the behavior depends on OS. I've heard that Windows supports real asynchronous data transporting, although I've never checked it by myself. So in this case, it makes sense. Another reason is to keep consistence: all perations should be coroutine.– Sraw
Nov 13 '18 at 17:16
@lhk
session.get()
gets the head of the response, which is enough to tell e.g. the status code and the characteristics of the response. response.text()
reads the body of the response, which can take additional time if the body is sufficiently long.– user4815162342
Nov 13 '18 at 18:08
@lhk
session.get()
gets the head of the response, which is enough to tell e.g. the status code and the characteristics of the response. response.text()
reads the body of the response, which can take additional time if the body is sufficiently long.– user4815162342
Nov 13 '18 at 18:08
add a comment |
Your testing code has three awaits (two explicit and one hidden in async with
) in series, so you don't get any parallel waiting. The code that tests the scenario you describe is something along the lines of:
async def download():
async with aiohttp.ClientSession() as session:
async with session.get("http://127.0.0.1:5000/") as response:
text = await response.text()
return text
async def main():
loop = asyncio.get_event_loop()
# have download start "in the background"
dltask = loop.create_task(download())
# now sleep
await asyncio.sleep(1)
# and now await the end of the download
text = await dltask
Running this coroutine should take the expected time.
add a comment |
Your testing code has three awaits (two explicit and one hidden in async with
) in series, so you don't get any parallel waiting. The code that tests the scenario you describe is something along the lines of:
async def download():
async with aiohttp.ClientSession() as session:
async with session.get("http://127.0.0.1:5000/") as response:
text = await response.text()
return text
async def main():
loop = asyncio.get_event_loop()
# have download start "in the background"
dltask = loop.create_task(download())
# now sleep
await asyncio.sleep(1)
# and now await the end of the download
text = await dltask
Running this coroutine should take the expected time.
add a comment |
Your testing code has three awaits (two explicit and one hidden in async with
) in series, so you don't get any parallel waiting. The code that tests the scenario you describe is something along the lines of:
async def download():
async with aiohttp.ClientSession() as session:
async with session.get("http://127.0.0.1:5000/") as response:
text = await response.text()
return text
async def main():
loop = asyncio.get_event_loop()
# have download start "in the background"
dltask = loop.create_task(download())
# now sleep
await asyncio.sleep(1)
# and now await the end of the download
text = await dltask
Running this coroutine should take the expected time.
Your testing code has three awaits (two explicit and one hidden in async with
) in series, so you don't get any parallel waiting. The code that tests the scenario you describe is something along the lines of:
async def download():
async with aiohttp.ClientSession() as session:
async with session.get("http://127.0.0.1:5000/") as response:
text = await response.text()
return text
async def main():
loop = asyncio.get_event_loop()
# have download start "in the background"
dltask = loop.create_task(download())
# now sleep
await asyncio.sleep(1)
# and now await the end of the download
text = await dltask
Running this coroutine should take the expected time.
answered Nov 13 '18 at 18:11
user4815162342user4815162342
61.2k591142
61.2k591142
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53285499%2fasyncio-aiohttp-overlapping-io-with-sleeping%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown