Assertion failed on a magnet link with `!p.is_seed()` on `master` branch
Hi, I have this magnet link
magnet:?xt=urn:btih:F9B4F6C8D8E1F8B13BB2468D1945A904285CE3C2&dn=Call+of+Duty%3A+Vanguard+%28v1.26+Campaign%2FZombies+%2B+Bonus+OST%2C+MULTi13%29+%5BFitGirl+Repack%2C+Selective+Download+-+from+41.8+GB%5D&tr=udp%3A%2F%2Fopentor.net%3A6969&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.theoks.net%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.ccp.ovh%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=https%3A%2F%2Ftracker.tamersunion.org%3A443%2Fannounce&tr=udp%3A%2F%2Fexplodie.org%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.bt4g.com%3A2095%2Fannounce&tr=udp%3A%2F%2Fbt2.archive.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fbt1.archive.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.filemail.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker1.bt.moack.co.kr%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Fopentracker.i2p.rocks%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce
Please include the following information:
version: 2.1.0.0-283cea598
file: '..\..\src\torrent.cpp'
line: 8998
function: check_invariant
expression: !p.is_seed()
stack:
0: 00007FF8F98649A7 PyInit_libtorrent +1001079
Minimal code to reproduce this issue:
import asyncio
import libtorrent as lt
MAGNET_LINK = r"magnet:?xt=urn:btih:F9B4F6C8D8E1F8B13BB2468D1945A904285CE3C2&dn=Call+of+Duty%3A+Vanguard+%28v1.26+Campaign%2FZombies+%2B+Bonus+OST%2C+MULTi13%29+%5BFitGirl+Repack%2C+Selective+Download+-+from+41.8+GB%5D&tr=udp%3A%2F%2Fopentor.net%3A6969&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.theoks.net%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.ccp.ovh%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=https%3A%2F%2Ftracker.tamersunion.org%3A443%2Fannounce&tr=udp%3A%2F%2Fexplodie.org%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.bt4g.com%3A2095%2Fannounce&tr=udp%3A%2F%2Fbt2.archive.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fbt1.archive.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.filemail.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker1.bt.moack.co.kr%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Fopentracker.i2p.rocks%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce"
async def wait_for_metadata(handle, timeout=20):
for i in range(timeout * 10):
if handle.has_metadata():
return True
await asyncio.sleep(0.1)
return False
async def main():
ses = lt.session()
ses.listen_on(6881, 6891)
params = lt.parse_magnet_uri(MAGNET_LINK)
params.save_path = "./downloads"
params.flags |= lt.torrent_flags.paused # Add paused
handle = ses.add_torrent(params)
print("Added torrent, waiting for metadata...")
has_meta = await wait_for_metadata(handle)
if not has_meta:
print("Timeout waiting for metadata")
return
print("Metadata fetched!")
# Already paused, but just in case
handle.pause()
print("Torrent paused.")
# Print status info
status = handle.status()
print(
f"Torrent status: state={status.state}, is_seed={handle.is_seed()}, num_peers={status.num_peers}"
)
if __name__ == "__main__":
asyncio.run(main())
setup script:
import json
import os
import platform
import re
import shutil
import sys
import zipfile
from io import BytesIO
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
def get_os_name_for_asset():
system = platform.system().lower()
if system == "darwin":
return "macos"
elif system == "linux":
return "ubuntu" # Linux builds use ubuntu naming here
elif system == "windows":
return "windows"
else:
raise RuntimeError(f"Unsupported OS: {system}")
def get_python_minor_version():
return sys.version_info.minor
def get_venv_site_packages():
prefix = sys.prefix # venv root when inside venv
py_version = f"python{sys.version_info.major}.{sys.version_info.minor}"
candidate_paths = [
os.path.join(prefix, "lib", py_version, "site-packages"), # Unix/macOS
os.path.join(prefix, "Lib", "site-packages"), # Windows
]
for path in candidate_paths:
if os.path.isdir(path):
return path
return prefix
def find_libtorrent_folder(root_dir):
for dirpath, dirnames, filenames in os.walk(root_dir):
if "libtorrent" in dirnames:
return os.path.join(dirpath, "libtorrent")
return None
def copy_folder(src_folder, dst_folder):
print(f"Copying from {src_folder} to {dst_folder} ...")
if os.path.exists(dst_folder):
shutil.rmtree(dst_folder)
shutil.copytree(src_folder, dst_folder)
def fetch_json(url):
print(f"Fetching JSON from {url} ...")
req = Request(url, headers={"User-Agent": "python-urllib"})
try:
with urlopen(req) as resp:
data = resp.read()
return json.loads(data.decode())
except HTTPError as e:
print(f"HTTP error: {e.code} {e.reason}")
except URLError as e:
print(f"URL error: {e.reason}")
return None
def download_file(url):
print(f"Downloading {url} ...")
req = Request(url, headers={"User-Agent": "python-urllib"})
try:
with urlopen(req) as resp:
return resp.read()
except HTTPError as e:
print(f"HTTP error: {e.code} {e.reason}")
except URLError as e:
print(f"URL error: {e.reason}")
return None
def main():
os_name = get_os_name_for_asset()
py_minor = get_python_minor_version()
pattern = re.compile(
rf"python-bindings-{os_name}-latest-py3\.{py_minor}-build\.zip"
)
print(f"Looking for assets matching OS='{os_name}', Python 3.{py_minor}")
GITHUB_API_RELEASES_URL = (
"https://api.github.com/repos/baseplate-admin/libtorrent-python/releases/latest"
)
release_data = fetch_json(GITHUB_API_RELEASES_URL)
if not release_data:
print("Failed to fetch release data.")
return
assets = release_data.get("assets", [])
matching_asset = None
for asset in assets:
name = asset.get("name", "")
if pattern.fullmatch(name):
matching_asset = asset
break
if not matching_asset:
print("No matching asset found for pattern:", pattern.pattern)
return
print(f"Found asset: {matching_asset['name']}")
download_url = matching_asset["browser_download_url"]
zip_bytes = download_file(download_url)
if not zip_bytes:
print("Failed to download the asset.")
return
tmp_extract_dir = "tmp_extract"
if os.path.exists(tmp_extract_dir):
shutil.rmtree(tmp_extract_dir)
os.makedirs(tmp_extract_dir)
with zipfile.ZipFile(BytesIO(zip_bytes)) as z:
print(f"Extracting to {tmp_extract_dir} ...")
z.extractall(tmp_extract_dir)
libtorrent_src = find_libtorrent_folder(tmp_extract_dir)
if not libtorrent_src:
print("Error: 'libtorrent' folder not found in extracted content.")
return
site_packages_path = get_venv_site_packages()
print("Detected site-packages folder:", site_packages_path)
dst = os.path.join(site_packages_path, "libtorrent")
copy_folder(libtorrent_src, dst)
shutil.rmtree(tmp_extract_dir)
print("Done.")
if __name__ == "__main__":
main()
Is it a regression?
The issue is non-existant on pypi libtorrent version 2.0.11.0, but exists with 2.1.0.0
I have narrowed it down:
import asyncio
import libtorrent as lt
import tempfile
MAGNET_LINK = r"magnet:?xt=urn:btih:F9B4F6C8D8E1F8B13BB2468D1945A904285CE3C2&dn=Call+of+Duty%3A+Vanguard+%28v1.26+Campaign%2FZombies+%2B+Bonus+OST%2C+MULTi13%29+%5BFitGirl+Repack%2C+Selective+Download+-+from+41.8+GB%5D&tr=udp%3A%2F%2Fopentor.net%3A6969&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.theoks.net%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.ccp.ovh%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=https%3A%2F%2Ftracker.tamersunion.org%3A443%2Fannounce&tr=udp%3A%2F%2Fexplodie.org%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.bt4g.com%3A2095%2Fannounce&tr=udp%3A%2F%2Fbt2.archive.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fbt1.archive.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.filemail.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker1.bt.moack.co.kr%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Fopentracker.i2p.rocks%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce"
async def main():
session = lt.session()
session.apply_settings({"listen_interfaces": "0.0.0.0:6881", "enable_dht": True})
with tempfile.TemporaryDirectory() as save_path:
params = lt.parse_magnet_uri(MAGNET_LINK)
params.save_path = save_path
params.storage_mode = lt.storage_mode_t.storage_mode_sparse
params.flags |= lt.torrent_flags.auto_managed
handle = session.add_torrent(params)
print("Waiting for metadata...")
while not handle.status().has_metadata:
await asyncio.sleep(1)
print("Metadata fetched!")
# === Metadata output ===
info = handle.get_torrent_info()
print(f"Torrent name: {info.name()}")
print(f"Total size: {info.total_size() / (1024**3):.2f} GB")
print(f"Piece size: {info.piece_length() // 1024} KB")
print(f"Number of pieces: {info.num_pieces()}")
print(f"Info hash (SHA1): {str(info.info_hash())}")
print(f"Files:")
for f in info.files():
print(f" - {f.path} ({f.size / (1024**2):.2f} MB)")
if __name__ == "__main__":
asyncio.run(main())
This sometimes fails, sometimes it passes.
Example of two runs:
(backend) PS C:\Programming\bittorrent-client\backend> python .\test.py
Waiting for metadata...
Metadata fetched!
(backend) PS C:\Programming\bittorrent-client\backend> python .\test.py
Waiting for metadata...
assertion failed. Please file a bugreport at https://github.com/arvidn/libtorrent/issues
Please include the following information:
version: 2.1.0.0-283cea598
file: '..\..\src\torrent.cpp'
line: 9003
function: check_invariant
expression: !p.is_seed()
stack:
0: 00007FF8FBFB4D67 PyInit_libtorrent +1002039
(backend) PS C:\Programming\bittorrent-client\backend> python .\test.py
Waiting for metadata...
Metadata fetched!
C:\Programming\bittorrent-client\backend\test.py:28: DeprecationWarning: get_torrent_info() is deprecated
info = handle.get_torrent_info()
Torrent name: Call of Duty - Vanguard [FitGirl Repack]
Total size: 60.43 GB
Piece size: 4096 KB
Number of pieces: 15472
Info hash (SHA1): f9b4f6c8d8e1f8b13bb2468d1945a904285ce3c2
Files:
C:\Programming\bittorrent-client\backend\test.py:36: DeprecationWarning: files() is deprecated
for f in info.files():
C:\Programming\bittorrent-client\backend\test.py:36: DeprecationWarning: __iter__ is deprecated
for f in info.files():
C:\Programming\bittorrent-client\backend\test.py:37: DeprecationWarning: file_entry is deprecated
print(f" - {f.path} ({f.size / (1024**2):.2f} MB)")
- Call of Duty - Vanguard [FitGirl Repack]\fg-01.bin (32753.40 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-02.bin (4966.37 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-03.bin (3209.79 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-04.bin (1568.91 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-05.bin (316.99 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-06.bin (0.26 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-arabic-and-chinese.bin (157.18 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-bonus-soundtrack.bin (158.26 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-brazilian.bin (1650.77 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-french.bin (1529.62 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-german.bin (1648.76 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-italian.bin (1626.05 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-japanese.bin (3889.91 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-korean.bin (1775.95 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-mexican.bin (1657.28 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-polish.bin (1635.27 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-russian.bin (1674.94 MB)
- Call of Duty - Vanguard [FitGirl Repack]\fg-optional-spanish.bin (1659.48 MB)
- Call of Duty - Vanguard [FitGirl Repack]\MD5\fitgirl-bins.md5 (0.00 MB)
- Call of Duty - Vanguard [FitGirl Repack]\MD5\QuickSFV.EXE (0.10 MB)
- Call of Duty - Vanguard [FitGirl Repack]\MD5\QuickSFV.ini (0.00 MB)
- Call of Duty - Vanguard [FitGirl Repack]\setup.exe (5.96 MB)
- Call of Duty - Vanguard [FitGirl Repack]\Verify BIN files before installation.bat (0.00 MB)
Patching the line out, libtorrent works as expected,
https://github.com/baseplate-admin/libtorrent-python/blob/main/patches/fix.patch
that link is broken. can you post the patch directly instead?
# This patch removes an assert that checks `!p.is_seed()` in non-seed peers.
# Reason: The check was redundant and caused false positives in edge cases.
# Check: https://github.com/arvidn/libtorrent/issues/7988
diff --git a/src/torrent.cpp b/src/torrent.cpp
index 9d046ab9a..a608187bd 100644
--- a/src/torrent.cpp
+++ b/src/torrent.cpp
@@ -8998,10 +8998,7 @@ namespace {
{
++seeds;
}
- else
- {
- TORRENT_ASSERT(!p.is_seed());
- }
+
}
for (auto const& j : p.request_queue())
Hey, sorry for broken link, i just removed the else statement