Repository: RafaelWO/unparallel (language: python)

Issue:
UP function does nothing
## 🐛 Bug Report

The progress bar is stuck at 0% and no http requests are made

## 🔬 How To Reproduce

Steps to reproduce the behavior:


### Code sample

```
async def index_blocks(block_from: int, block_to: int):
    """Index a block range"""
    global TOTAL_TXS, TOTAL_REVERTED_TXS
    print(f"Indexing blocks from {block_from} to {block_to}")
    urls = [f"blocks/{block}?expanded=true" for block in range(block_from, block_to)]
    block_responses = await up(urls, method="GET", base_url=THOR_URL, timeout=5, max_retries_on_timeout=2)

def get_contract_txs():
    best_block = get_best_block()
    loop = asyncio.new_event_loop()
    index_from = best_block - (INDEX_DAYS * BLOCKS_PER_DAY)
    loop.run_until_complete(index_blocks(index_from, best_block))


def main():
    get_contract_txs()
    print(f"Total transactions for VOT3 or B3TR: {TOTAL_TXS}")
    print(f"Total transactions reverted: {TOTAL_REVERTED_TXS}")
    print(f"Clauses to debug trace: {len(REVERTED_TXS)}")


if __name__ == '__main__':
    main()
```


### Environment

* OS: macOS
* 3.10.12
* 
```bash
python --version
```

### Screenshots



## 📈 Expected behavior



## 📎 Additional context




Discussion / hints:
Hello @claytonneal, thank you for your interest in our work!

If this is a bug report, please provide screenshots and **minimum viable code to reproduce your issue**, otherwise we can not help you.
Hey @claytonneal, can you provide a minimal reproducible example that I can run? (I don't know the values of the global variables in your example.) 
Maybe you can also add the output you get when running your code.

I hope we can resolve your issue together :slightly_smiling_face: 

@claytonneal I managed to run your code with some modifications - essentially providing dummy values for your global variables and targeting https://httpbin.org/ - and it works without issues:

```python
import asyncio

from unparallel import up

THOR_URL = "https://httpbin.org"


async def index_blocks(block_from: int, block_to: int):
    """Index a block range"""
    global TOTAL_TXS, TOTAL_REVERTED_TXS
    print(f"Indexing blocks from {block_from} to {block_to}")
    urls = [f"anything/{block}?expanded=true" for block in range(block_from, block_to)]
    block_responses = await up(
        urls, method="GET", base_url=THOR_URL, timeout=5, max_retries_on_timeout=2
    )


def get_contract_txs():
    best_block = 87  # get_best_block()
    loop = asyncio.new_event_loop()
    # index_from = best_block - (INDEX_DAYS * BLOCKS_PER_DAY)
    index_from = 4
    loop.run_until_complete(index_blocks(index_from, best_block))


def main():
    get_contract_txs()
    # print(f"Total transactions for VOT3 or B3TR: {TOTAL_TXS}")
    # print(f"Total transactions reverted: {TOTAL_REVERTED_TXS}")
    # print(f"Clauses to debug trace: {len(REVERTED_TXS)}")


if __name__ == "__main__":
    main()
```

Here is the output:
```
python gh-157.py 
Indexing blocks from 4 to 87
Making async requests: 100%|████████████████████████████| 83/83 [00:01<00:00, 76.20it/s]
```

How many requests are you trying to do? Maybe you have to modify the limits and timeouts?

Fix patch:
diff --git a/.safety-policy.yml b/.safety-policy.yml
new file mode 100644
index 0000000..33d9a28
--- /dev/null
+++ b/.safety-policy.yml
@@ -0,0 +1,41 @@
+version: '3.0'
+
+scanning-settings:
+  max-depth: 6
+  exclude: []
+  include-files: []
+  system:
+    targets: []
+
+
+report:
+  dependency-vulnerabilities:
+    enabled: true
+    auto-ignore-in-report:
+      python:
+        environment-results: true
+        unpinned-requirements: true
+      cvss-severity: []
+      vulnerabilities:
+        70612:
+          reason: The maintainer and multiple third parties believe that this vulnerability isn't valid because users shouldn't use untrusted templates without sandboxing.
+          expires: '2024-09-01'
+
+
+fail-scan-with-exit-code:
+  dependency-vulnerabilities:
+    enabled: true
+    fail-on-any-of:
+      cvss-severity:
+        - critical
+        - medium
+        - high
+      exploitability:
+        - critical
+        - medium
+        - high
+
+security-updates:
+  dependency-vulnerabilities:
+    auto-security-updates-limit:
+      - patch
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f60807..1c22594 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,8 +5,22 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+<!--
+Types of changes:
+    Added for new features.
+    Changed for changes in existing functionality.
+    Deprecated for soon-to-be removed features.
+    Removed for now removed features.
+    Fixed for any bug fixes.
+    Security in case of vulnerabilities.
+-->
+
 ## [Unreleased]
 
+### Fixed
+* Fix synchronization of async tasks and HTTPX timeouts by using a semaphore with the
+same value as ``max_connections`` (#197) @RafaelWO
+
 ## [0.3.0] - 2024-03-02
 
 ### Added
@@ -41,7 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 * Upgrade multiple packages manually (#133) @RafaelWO
 * Bump pytest from 8.0.1 to 8.0.2 (#139) @dependabot
 
-### Fixed 
+### Fixed
 * Fix: Code in Docs (#138) @RafaelWO
 
 
diff --git a/mkdocs.yml b/mkdocs.yml
index fcc379b..6653671 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -33,6 +33,8 @@ theme:
 extra:
   version:
     provider: mike
+    alias: true
+    default: latest
 plugins:
   - search
   - mkdocstrings:
diff --git a/poetry.lock b/poetry.lock
index fd4c259..269f10e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -16,13 +16,13 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""}
 
 [[package]]
 name = "anyio"
-version = "4.3.0"
+version = "4.4.0"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
-    {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
+    {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
+    {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
 ]
 
 [package.dependencies]
@@ -711,13 +711,13 @@ license = ["ukkonen"]
 
 [[package]]
 name = "idna"
-version = "3.6"
+version = "3.7"
 description = "Internationalized Domain Names in Applications (IDNA)"
 optional = false
 python-versions = ">=3.5"
 files = [
-    {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
-    {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
+    {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+    {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
 ]
 
 [[package]]
@@ -1642,7 +1642,6 @@ files = [
     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
     {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
     {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
-    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
     {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
     {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
     {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@@ -1809,13 +1808,13 @@ files = [
 
 [[package]]
 name = "requests"
-version = "2.31.0"
+version = "2.32.3"
 description = "Python HTTP for Humans."
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
-    {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+    {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
+    {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
 ]
 
 [package.dependencies]
@@ -1907,24 +1906,24 @@ python-versions = ">=3.6"
 files = [
     {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"},
     {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"},
+    {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"},
     {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"},
-    {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"},
     {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"},
     {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"},
     {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"},
     {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"},
     {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"},
     {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"},
+    {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"},
     {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"},
-    {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"},
     {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"},
     {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"},
     {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"},
     {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"},
     {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"},
     {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"},
+    {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"},
     {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"},
-    {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"},
     {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"},
     {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"},
     {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"},
@@ -1932,7 +1931,7 @@ files = [
     {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"},
     {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"},
     {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"},
-    {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"},
+    {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"},
     {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"},
     {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"},
     {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"},
@@ -1940,7 +1939,7 @@ files = [
     {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"},
     {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"},
     {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"},
-    {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"},
+    {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"},
     {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"},
     {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"},
     {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"},
@@ -1948,7 +1947,7 @@ files = [
     {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"},
     {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"},
     {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"},
-    {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"},
+    {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"},
     {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"},
     {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"},
     {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"},
@@ -2121,13 +2120,13 @@ files = [
 
 [[package]]
 name = "tqdm"
-version = "4.66.2"
+version = "4.66.4"
 description = "Fast, Extensible Progress Meter"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"},
-    {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"},
+    {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"},
+    {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"},
 ]
 
 [package.dependencies]
diff --git a/unparallel/unparallel.py b/unparallel/unparallel.py
index 50ccef1..e3bc883 100644
--- a/unparallel/unparallel.py
+++ b/unparallel/unparallel.py
@@ -24,7 +24,21 @@
 DEFAULT_TIMEOUT = httpx.Timeout(timeout=10)
 DEFAULT_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
 
-SEMAPHORE_COUNT = 1000
+MAX_SEMAPHORE_COUNT = 1000
+
+
+class UseMaxConnections:
+    """
+    For the parameter `semaphore` in the `up()` signature we need to be able to
+    differentiate between the default "unset" state and `None`.
+
+    The default value `semaphore=USE_MAX_CONNECTIONS` results in the value being the
+    same as `max_connections`. However, the user can also set `semaphore` to some
+    integer or `None`.
+    """
+
+
+USE_MAX_CONNECTIONS = UseMaxConnections()
 
 
 @dataclass
@@ -65,15 +79,15 @@ async def single_request(
         client (AsyncClient): The httpx client.
         url (str): The URL after the base URI.
         method (str): The HTTP method.
-        json (Optional[Any], optional): The JSON payload. Defaults to None.
+        json (Optional[Any], optional): The JSON payload. Defaults to ``None``.
         response_fn (Optional[Callable[[httpx.Response], Any]]): The function to apply
             on every response of the HTTP requests. Defaults to ``httpx.Response.json``.
         max_retries_on_timeout (int): The maximum number retries if the requests fails
-            due to a timeout (``httpx.TimeoutException``). Defauls to 3.
+            due to a timeout (``httpx.TimeoutException``). Defaults to ``3``.
         raise_for_status (bool): If True, ``.raise_for_status()`` is called on the
             response.
         semaphore (Optional[asyncio.Semaphore]): A semaphore object to synchronize
-            the HTTP request. Defaults to None (-> nullcontext).
+            the HTTP request. Defaults to ``None`` (-> nullcontext).
 
     Returns:
         Tuple[int, Any]: A tuple of the index and the JSON response.
@@ -129,6 +143,7 @@ async def request_urls(
     timeouts: httpx.Timeout = DEFAULT_TIMEOUT,
     client: Optional[httpx.AsyncClient] = None,
     progress: bool = True,
+    semaphore_value: Optional[int] = None,
 ) -> List[Any]:
     """
     Asynchronously issues requests to the specified URL(s)
@@ -148,12 +163,14 @@ async def request_urls(
         raise_for_status (bool): If True, ``.raise_for_status()`` is called on every
             response.
         max_retries_on_timeout (int): The maximum number retries if the requests fails
-            due to a timeout (``httpx.TimeoutException``). Defauls to 3.
+            due to a timeout (``httpx.TimeoutException``). Defaults to ``3``.
         limits (httpx.Limits): The limits configuration for ``httpx``.
         timeouts (httpx.Timeout): The timeout configuration for ``httpx``.
         client (Optional[httpx.AsyncClient]): An instance of ``httpx.AsyncClient`` to be
             used for creating the HTTP requests.
         progress (bool): Whether to show a progress bar. Defaults to ``True``.
+        semaphore_value: (Optional[int]): The value for the ``asyncio.Semaphore`` object
+            that syncronizes the calls to HTTPX. Defaults to ``None``.
 
     Returns:
         A list of the response data per request in the same order as the input
@@ -161,11 +178,7 @@ async def request_urls(
     """
     tasks = []
     results = []
-    # If you want to do lot's of web requests (over 10k), it is really hard to avoid
-    # getting timeout errors. Adding a semaphore with a limit of 1k drastically reduced
-    # the amount of timeouts. As one will likely use fewer parallel open connections
-    # (max_connections) than 1k, it should not slow down the HTTP requests.
-    semaphore = asyncio.Semaphore(SEMAPHORE_COUNT)
+    semaphore = asyncio.Semaphore(semaphore_value) if semaphore_value else None
 
     logging.debug(
         f"Issuing {len(urls)} {method.upper()} request(s) to base URL '{base_url}' "
@@ -226,6 +239,7 @@ async def up(
     timeouts: Optional[httpx.Timeout] = None,
     client: Optional[httpx.AsyncClient] = None,
     progress: bool = True,
+    semaphore_value: Union[int, UseMaxConnections, None] = USE_MAX_CONNECTIONS,
 ) -> List[Any]:
     """Creates async web requests to the specified URL(s) using ``asyncio``
     and ``httpx``.
@@ -239,13 +253,13 @@ async def up(
         method (str): HTTP method to use - one of ``GET``, ``OPTIONS``, ``HEAD``,
             ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. Defaults to ``GET``.
         base_url (Optional[str]):  The base URL of the target API/service. Defaults to
-            None.
+            ``None``.
         headers (Optional[Dict[str, Any]], optional): A dictionary of headers to use.
-            Defaults to None.
+            Defaults to ``None``.
         payloads (Optional[Any], optional): A list of JSON payloads (dictionaries) e.g.
             for HTTP post requests. Used together with ``urls``. If one payload but
             multiple URLs are supplied, that payload is used for all requests.
-            Defaults to None.
+            Defaults to ``None``.
         response_fn (Optional[Callable[[httpx.Response], Any]]): The function (callback)
             to apply on every response of the HTTP requests. This can be an existing
             function of ``httpx.Response`` like ``.json()`` or ``.read()``, or a custom
@@ -254,13 +268,13 @@ async def up(
             Defaults to ``httpx.Response.json``.
         flatten_result (bool): If True and the response per request is a list,
             flatten that list of lists. This is useful when using paging.
-            Defaults to False.
+            Defaults to ``False``.
         max_connections (int): The total number of simultaneous TCP
-            connections. Defaults to 100. This is passed into ``httpx.Limits``.
+            connections. Defaults to ``100``. This is passed into ``httpx.Limits``.
         timeout (int): The timeout for requests in seconds. Defaults to 10.
             This is passed into ``httpx.Timeout``.
         max_retries_on_timeout (int): The maximum number retries if the requests fails
-            due to a timeout (``httpx.TimeoutException``). Defauls to 3.
+            due to a timeout (``httpx.TimeoutException``). Defaults to ``3``.
         raise_for_status (bool): If True, ``.raise_for_status()`` is called on overy
             response.
         limits (Optional[httpx.Limits]): The limits configuration for ``httpx``.
@@ -270,9 +284,12 @@ async def up(
         client (Optional[httpx.AsyncClient]): An instance of ``httpx.AsyncClient`` to be
             used for creating the HTTP requests. **Note that if you pass a client, all
             other options that parametrize the client (``base_url``, ``headers``,
-            ``limits``, and ``timeouts``) are ignored**. Defaults to None.
-        progress (bool): If set to True, progress bar is shown.
-            Defaults to True.
+            ``limits``, and ``timeouts``) are ignored**. Defaults to ``None``.
+        progress (bool): If set to ``True``, progress bar is shown.
+            Defaults to ``True``.
+        semaphore_value: (Union[int, UseMaxConnections, None]): The value for the
+            ``asyncio.Semaphore`` object that syncronizes the calls to HTTPX. Defaults
+            to the number of ``max_connections``.
 
     Raises:
         ValueError: If the HTTP method is not valid.
@@ -319,6 +336,16 @@ async def up(
         else:
             limits = DEFAULT_LIMITS
 
+    # After some benchmarking we discovered that syncronizing the HTTP requests with a
+    # semaphore object that has the same value as the max_connections gives the best
+    # performance.
+    # Also, limiting the semaphore value to a maximum of 1k drastically reduced the
+    # amount of timeouts.
+    if isinstance(semaphore_value, UseMaxConnections):
+        semaphore_value = min(
+            max_connections or MAX_SEMAPHORE_COUNT, MAX_SEMAPHORE_COUNT
+        )
+
     return await request_urls(
         urls=urls,
         method=method,
@@ -333,4 +360,5 @@ async def up(
         timeouts=timeouts,
         client=client,
         progress=progress,
+        semaphore_value=semaphore_value,
     )


Test patch:
diff --git a/tests/test_unparallel.py b/tests/test_unparallel.py
index f712304..136067b 100644
--- a/tests/test_unparallel.py
+++ b/tests/test_unparallel.py
@@ -18,6 +18,10 @@
 BASE_URL = "http://test.com"
 
 
+def get_kwargs(*args, **kwargs):
+    return kwargs
+
+
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
     "url, method, payload",
@@ -238,10 +242,9 @@ async def test_up_wrong_method():
 )
 @mock.patch("unparallel.unparallel.request_urls")
 @pytest.mark.asyncio
-async def test_config(request_urls_mock, up_kwargs, expected_timeouts, expected_limits):
-    def get_kwargs(*args, **kwargs):
-        return kwargs
-
+async def test_httpx_config(
+    request_urls_mock, up_kwargs, expected_timeouts, expected_limits
+):
     request_urls_mock.side_effect = get_kwargs
 
     options = await up("/bar", base_url="foobar", **up_kwargs)
@@ -249,6 +252,28 @@ def get_kwargs(*args, **kwargs):
     assert options["limits"] == expected_limits
 
 
+@pytest.mark.parametrize(
+    "up_kwargs, expected_sem_value",
+    [
+        ({}, 100),
+        ({"max_connections": 10}, 10),
+        ({"max_connections": 2000}, 1000),
+        ({"max_connections": None}, 1000),
+        ({"semaphore_value": 42}, 42),
+        ({"max_connections": 100, "semaphore_value": 42}, 42),
+        ({"semaphore_value": None}, None),
+    ],
+)
+@mock.patch("unparallel.unparallel.request_urls")
+@pytest.mark.asyncio
+async def test_up_semaphore_value(request_urls_mock, up_kwargs, expected_sem_value):
+    request_urls_mock.side_effect = get_kwargs
+
+    options = await up("https://example.com", **up_kwargs)
+
+    assert options["semaphore_value"] == expected_sem_value
+
+
 @pytest.mark.asyncio
 async def test_up_with_client(respx_mock):
     auth = httpx.BasicAuth("foo", "bar")