httpx + http2 raises a lot of exceptions when parallelized with ThreadPoolExecuton
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.5k
- Forks
- 76
- Avg merge
- 8h 59m
- Merged PRs (30d)
- 24
Description
Originally opened by
@takumi2786on 2023-12-13 09:22:04 in encode/httpx
The starting point for issues should usually be a discussion...
https://github.com/encode/httpx/discussions
Possible bugs may be raised as a "Potential Issue" discussion, feature requests may be raised as an "Ideas" discussion. We can then determine if the discussion needs to be escalated into an "Issue" or not.
This will help us ensure that the "Issues" list properly reflects ongoing or needed work on the project.
- Initially raised as discussion #...
Problem
I tried to use httpx as http2 client and I want to execute multiple requests in parallel.
First, I tried to parallelize with ThreadPoolExecutor, but it threw a lot of exceptions.
Then I used Asyncio and it worked.
reproducion
server
package main
import (
"fmt"
"log"
"net/http"
"time"
"golang.org/x/net/http2"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
fmt.Println("Hello, HTTP/2!")
fmt.Fprintf(w, "Hello, HTTP/2!")
})
server := &http.Server{
Addr: ":8080",
}
http2.ConfigureServer(server, &http2.Server{})
log.Fatal(server.ListenAndServeTLS("../server.crt", "../server.key"))
}
client
import asyncio
import httpx
from concurrent.futures import ThreadPoolExecutor, as_completed
from logging import basicConfig
basicConfig(level='DEBUG')
PARRALEL = 50
def request(client: httpx.Client):
response = client.get(
'https://localhost:8080/',
timeout=httpx.Timeout(1.5),
)
print(response.http_version)
print(response.text)
return response
def batch_request():
client = httpx.Client(
http2=True,
verify=False,
)
futures = []
with ThreadPoolExecutor(max_workers=100) as executor:
for i in range(PARRALEL):
futures.append(executor.submit(request, client))
for future in as_completed(futures, timeout=5.0):
result = future.result()
print(result.http_version)
print(result.text)
async def request_async(client: httpx.AsyncClient):
response = await client.get(
'https://localhost:8080/',
timeout=httpx.Timeout(1.5),
)
print(response.http_version)
print(response.text)
return response
async def batch_request_async():
async with httpx.AsyncClient(
http2=True,
verify=False,
) as client:
tasks = []
for i in range(PARRALEL):
task = asyncio.create_task(request_async(client))
tasks.append(task)
for task in tasks:
try:
await task
except Exception as e:
print("exception!!!!", e)
def main_v2():
loop = asyncio.get_event_loop()
loop.run_until_complete(batch_request_async())
if __name__ == "__main__":
# raise many error
batch_request()
# this works well
# loop = asyncio.get_event_loop()
# loop.run_until_complete(batch_request_async())
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Python client reproduction using httpx.Client(http2=True) and ThreadPoolExecutor, then compare its behavior with the provided asyncio version and Go HTTP/2 server. The issue does not name repository files or tests; done means isolating the parallel-client failure and documenting or correcting the behavior with coverage for the reported scenario.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, python
- Domain
- api, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100