teloxide icon indicating copy to clipboard operation
teloxide copied to clipboard

`operation timed out` when uploading a large video file

Open lorenzo9uerra opened this issue 2 years ago • 3 comments

While I'm uploading a video of at least 50MB (I guess this depends on the speed of your connection, to reproduce the issue it has to take more than 17 seconds) the program stops after exactly 17 seconds from the start.

I tried this code:

use teloxide::prelude2::*;
use teloxide::types::InputFile;
use std::env;
use serde::{Deserialize, Serialize};
use serde_yaml::{self};

use std::{error::Error as StdError, fmt};

#[derive(Debug)]
pub enum Error {
    /// Some unspecified error.
    Any(Box<dyn StdError + Send + Sync + 'static>),
    TokioError {
        description: String,
    },
    UnknownError,
    UnreadableMessage,
    FileNotFound {
        path: String,
    },
    RequestError {
        description: String,
    },
    ParsingError {
        description: String,
    },
    MissingChatId,
    NoInput,
}

impl Error {
    /// Prints error to stderr and exits the program.
    pub fn exit(self) {
        eprintln!("{}", self);
        std::process::exit(1);
    }
}

impl<'a> fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::RequestError { ref description } | Error::TokioError { ref description } => {
                write!(f, "\nMessage failed to send due to:\n\t{}", description)
            }
            Error::FileNotFound { ref path } => {
                write!(f, "\nCould not find file in path:\n\t{}", path)
            }
            Error::MissingChatId => {
                write!(f, "\nChat ID not found in flags or TEPE_TELEGRAM_CHAT_ID")
            }
            Error::NoInput => write!(f, "\nNo input was given"),
            Error::ParsingError { ref description } => {
                write!(f, "\nError from parsing:\n\t{}", description)
            }
            Error::UnreadableMessage => write!(f, "\nIssue parsing message"),
            _ => write!(f, "\nTODO: add error description"),
        }
    }
}

use teloxide::RequestError;
impl From<RequestError> for Error {
    fn from(error: RequestError) -> Self {
        Error::RequestError {
            description: format!("{}", error),
        }
    }
}

use tokio::io::Error as TokioError;
impl From<TokioError> for Error {
    fn from(error: TokioError) -> Self {
        Error::TokioError {
            description: format!("{}", error),
        }
    }
}

impl From<std::num::ParseIntError> for Error {
    fn from(error: std::num::ParseIntError) -> Self {
        Error::ParsingError {
            description: error.to_string(),
        }
    }
}

pub trait CliExit<T> {
    fn cli_expect(self, message: &str) -> T;
}

impl<T, E> CliExit<T> for Result<T, E> {
    fn cli_expect(self, message: &str) -> T {
        match self {
            Ok(t) => t,
            Err(_e) => {
                eprintln!("{}", message);
                std::process::exit(1);
            }
        }
    }
}

impl<T> CliExit<T> for Option<T> {
    fn cli_expect(self, message: &str) -> T {
        match self {
            Some(t) => t,
            None => {
                eprintln!("{}", message);
                std::process::exit(1);
            }
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct Config {
    token: String,
    chat_id: i64,
}

#[tokio::main]
async fn main() {
    run().await.unwrap_or_else(|error| error.exit());
}

async fn run() -> Result<(), Error> {
    let f = std::fs::File::open("tgconfig.yml").expect("Could not open file.");
    let scrape_config: Config = serde_yaml::from_reader(f).expect("Could not read values.");
    teloxide::enable_logging!();
    log::info!("Starting regs_bot...");
    let args: Vec<String> = env::args().collect();

    let bot = Bot::new(scrape_config.token);
    if args.len() != 3 {
        println!("Insert file path and caption");
    }
    else {
        bot.send_video(scrape_config.chat_id, InputFile::file(&args[1])).caption(&args[2]).send().await?;
    }
    Ok(())
}

I expected to see the video uploaded to the chat_id specified.

Instead, the upload stopped after exactly 17 seconds with the message "operation timed out". I tried to downgrade teloxide-core to 0.4.1 but it didn't make any difference. I also tried to downgrade teloxide version to 0.6.0, no difference.

Dependencies

  • teloxide = { version = "0.7.0", features = ["macros", "auto-send"] }
  • log = "0.4"
  • pretty_env_logger = "0.4.0"
  • tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
  • serde = { version = "1.0", features = ["derive"] }
  • serde_yaml = "0.8"

lorenzo9uerra avatar Feb 23 '22 13:02 lorenzo9uerra

The problem is that teloxide uses 17 second network timeout by default and iirc we did this since 0.1.

You can workaround this issue by increasing the timeout:

let client = net::default_reqwest_settings().timeout(Duration::from_secs(30));
let bot = Bot::with_client(scrape_config.token, client);

Some ideas how we may fix this in the future:

  • Requests that send files may increase their timeout based on some heuristic based on their size (though I'm not sure if we can always get the size of a file and I'm even less sure how to properly choose the function)
  • We can add functions to Request trait, that would allow to changing timeout on by-request basis

Both of these are blocked on the same thing as #526 (see https://github.com/teloxide/teloxide/issues/526#issuecomment-1048754931).

WaffleLapkin avatar Feb 23 '22 13:02 WaffleLapkin

@WaffleLapkin, is this fixed?

Hirrolot avatar Apr 18 '22 16:04 Hirrolot

@Hirrolot no, this is still blocked on https://github.com/seanmonstar/reqwest/pull/1477

WaffleLapkin avatar Apr 18 '22 16:04 WaffleLapkin