From 610fe35d334f02d3c32d8ffef4e074ade07e3d05 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Mar 2026 02:24:24 +0000 Subject: [PATCH] Add configurable timeout parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds --timeout and --connect-timeout CLI options to control: - --timeout: How long to wait for piece downloads (default: 30s) - --connect-timeout: How long to wait for peer connections (default: 10s) This allows users to wait longer for rare seeders or slow peers, which is essential for archival torrents with limited availability. Examples: tornado --timeout 600 file.torrent # 10min per piece tornado --connect-timeout 30 file.torrent # 30s to connect 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/bin/cli.ml | 27 ++++++++++++++++++++------- src/lib/torrent_client.ml | 31 ++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/bin/cli.ml b/src/bin/cli.ml index 80ae3ec..9dcdea1 100644 --- a/src/bin/cli.ml +++ b/src/bin/cli.ml @@ -4,9 +4,11 @@ let usage = "Usage: tornado [OPTIONS] \n\ \nDownload files from BitTorrent networks.\n\ \n\ OPTIONS:\n\ - \ -o Set output file/directory name (default: use torrent name)\n\ - \ --verbose Output debug information\n\ - \ --help, -h Show this help message\n\ + \ -o Set output file/directory name (default: use torrent name)\n\ + \ --timeout Timeout for piece downloads (default: 30.0)\n\ + \ --connect-timeout Timeout for peer connections (default: 10.0)\n\ + \ --verbose Output debug information\n\ + \ --help, -h Show this help message\n\ \n\ FEATURES:\n\ \ • Multi-file torrents - automatically creates directory structure\n\ @@ -15,9 +17,16 @@ let usage = "Usage: tornado [OPTIONS] \n\ \ • Seeding - announces as seeder after completing download\n\ \n\ EXAMPLES:\n\ - \ tornado file.torrent # Download using torrent's name\n\ - \ tornado file.torrent -o myfile # Download with custom name\n\ - \ tornado --verbose file.torrent # Download with debug output\n\ + \ tornado file.torrent # Download using torrent's name\n\ + \ tornado file.torrent -o myfile # Download with custom name\n\ + \ tornado --verbose file.torrent # Download with debug output\n\ + \ tornado --timeout 300 file.torrent # Wait up to 5 minutes per piece\n\ + \ tornado --connect-timeout 30 file.torrent # Wait 30s to connect to peers\n\ + \n\ + TIMEOUTS:\n\ + \ For torrents with rare seeders, increase timeouts to wait longer:\n\ + \ --timeout 600 # 10 minutes per piece\n\ + \ --connect-timeout 60 # 1 minute to connect\n\ \n\ RESUME:\n\ \ If a download is interrupted, simply run the same command again.\n\ @@ -27,11 +36,15 @@ let verbose = ref false let output_file : string option ref = ref None let input_file = ref "" let show_help = ref false +let timeout = ref 30.0 +let connect_timeout = ref 10.0 let apply_output_file str = output_file := Some str let spec_list = [ "--verbose", Arg.Set verbose, "" ; "-o", Arg.String apply_output_file, "" + ; "--timeout", Arg.Set_float timeout, "" + ; "--connect-timeout", Arg.Set_float connect_timeout, "" ; "--help", Arg.Set show_help, "" ; "-h", Arg.Set show_help, "" ] ;; @@ -53,5 +66,5 @@ let () = ); Log.setup_log (Some (if !verbose then Debug else App)); let torrent_file = Torrent_file.open_file !input_file in - Lwt_main.run (Torrent_client.download_file !output_file torrent_file) + Lwt_main.run (Torrent_client.download_file !output_file torrent_file !timeout !connect_timeout) ;; diff --git a/src/lib/torrent_client.ml b/src/lib/torrent_client.ml index f89f203..78ab70e 100644 --- a/src/lib/torrent_client.ml +++ b/src/lib/torrent_client.ml @@ -111,7 +111,7 @@ let with_timeout timeout f = ] ;; -let try_download_piece (client : Client.t) (pw : piece_work) torrent = +let try_download_piece timeout (client : Client.t) (pw : piece_work) torrent = let state = { requested = ref 0 ; downloaded = ref 0 @@ -124,7 +124,7 @@ let try_download_piece (client : Client.t) (pw : piece_work) torrent = "Try to download piece %d of %d" (pw.index + 1) (Array.length torrent.piece_hashes)); - with_timeout 30.0 (fun () -> + with_timeout timeout (fun () -> download_piece client pw torrent state >>= fun piece_buf -> Logs.info (fun m -> m @@ -143,13 +143,14 @@ let check_integrity (pw : piece_work) buf = ;; let download_piece_task + timeout client torrent (push_work : piece_work option -> unit) (push_result : piece_result option -> unit) pw = - try_download_piece client pw torrent >>= fun piece_buf -> + try_download_piece timeout client pw torrent >>= fun piece_buf -> let integrity_result = check_integrity pw piece_buf in match integrity_result with | Error (`Error e) -> @@ -170,6 +171,7 @@ let download_piece_task ;; let rec download_torrent_worker + timeout (torrent : t) (client : Client.t) (work_stream : piece_work Lwt_stream.t) @@ -194,6 +196,7 @@ let rec download_torrent_worker Lwt.return_unit) else download_piece_task + timeout client torrent push_work @@ -206,27 +209,31 @@ let rec download_torrent_worker Logs.err (fun m -> m "%a" Fmt.exn ex); Lwt.return_unit) >>= fun () -> - download_torrent_worker torrent client work_stream push_work push_result + download_torrent_worker timeout torrent client work_stream push_work push_result ;; let connect_and_download_torrent + timeout + connect_timeout torrent peer (work_stream : piece_work Lwt_stream.t) (push_work : piece_work option -> unit) (push_result : piece_result option -> unit) = - with_timeout 4.0 (fun () -> Client.connect peer torrent.info_hash torrent.peer_id) + with_timeout connect_timeout (fun () -> Client.connect peer torrent.info_hash torrent.peer_id) >>= fun client_result -> let client = Result.get_ok client_result in Logs.debug (fun m -> m "Completed handshake with %s\n" (Ipaddr.V4.to_string peer.ip)); Client.send_unchoke client >>= fun () -> Client.send_interested client >>= fun () -> - download_torrent_worker torrent client work_stream push_work push_result + download_torrent_worker timeout torrent client work_stream push_work push_result ;; let rec download_torrent_by_peers + timeout + connect_timeout torrent (peers : Peers.t list) (work_stream : piece_work Lwt_stream.t) @@ -240,6 +247,8 @@ let rec download_torrent_by_peers Lwt.catch (fun () -> connect_and_download_torrent + timeout + connect_timeout torrent peers_head work_stream @@ -247,6 +256,8 @@ let rec download_torrent_by_peers push_result) (fun _ex -> Lwt.return_unit)); download_torrent_by_peers + timeout + connect_timeout torrent peers_tail work_stream @@ -309,7 +320,7 @@ let remove_state_file output_file = Unix.unlink path ;; -let download_torrent (torrent : t) file_name completed_pieces = +let download_torrent timeout connect_timeout (torrent : t) file_name completed_pieces = let open Lwt.Infix in let final_buf = Bytes.create torrent.length in let pieces_hashes_len = Array.length torrent.piece_hashes in @@ -337,6 +348,8 @@ let download_torrent (torrent : t) file_name completed_pieces = (* Start peer download tasks *) Lwt.async (fun () -> download_torrent_by_peers + timeout + connect_timeout torrent torrent.peers work_stream @@ -408,7 +421,7 @@ let write_multifile base_dir files final_buf = write_files 0 files ;; -let download_file output_file torrent_file = +let download_file output_file torrent_file timeout connect_timeout = let open Torrent_file in let base_name = match output_file, torrent_file.name with @@ -443,7 +456,7 @@ let download_file output_file torrent_file = (torrent_file.piece_length |> Int64.to_int) total_len in - download_torrent torrent base_name completed_pieces >>= fun final_buf -> + download_torrent timeout connect_timeout torrent base_name completed_pieces >>= fun final_buf -> (* Write File(s) *) (match torrent_file.file_mode with | SingleFile _ -> -- 2.43.0