diff -Nru pushpin-1.38.0/.github/workflows/test.yml pushpin-1.39.1/.github/workflows/test.yml --- pushpin-1.38.0/.github/workflows/test.yml 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/.github/workflows/test.yml 2024-03-18 18:42:24.000000000 +0000 @@ -38,7 +38,7 @@ run: rustup component add clippy shell: bash - name: Install audit - run: cargo install cargo-audit + run: cargo install --version 0.17.6 --locked cargo-audit shell: bash - name: Install deps run: sudo apt-get update && sudo apt-get install -y make g++ libssl-dev libzmq3-dev qtbase5-dev libboost-dev @@ -46,11 +46,14 @@ run: cargo fmt --check shell: bash - name: build - run: cargo fetch && make + run: RUSTFLAGS="-D warnings" make shell: bash - name: check run: RUSTFLAGS="-D warnings" make check shell: bash + - name: cargo bench --no-run + run: RUSTFLAGS="-D warnings" cargo bench --no-run + shell: bash - name: clippy run: cargo clippy -- -D warnings shell: bash diff -Nru pushpin-1.38.0/CHANGELOG.md pushpin-1.39.1/CHANGELOG.md --- pushpin-1.38.0/CHANGELOG.md 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/CHANGELOG.md 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,19 @@ Pushpin Changelog ================= +v. 1.39.1 (2024-03-18) + + * Regenerate pushpin.conf.inst post-build to ensure up-to-date configuration. + * Update legacy runner use revised Qt linking logic, aligning with main branch improvements. + +v. 1.39.0 (2024-03-14) + + * Add support for multiple proxy worker threads. + * New config option: workers (under [proxy]). + * Fix memory leak when proxying requests. + * Various build system fixes/improvements. + * Use Boost for signals & slots to reduce dependence on Qt's event loop. + v. 1.38.0 (2024-01-08) * Publish refresh action for triggering WebSocket-over-HTTP requests. diff -Nru pushpin-1.38.0/Cargo.lock pushpin-1.39.1/Cargo.lock --- pushpin-1.38.0/Cargo.lock 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/Cargo.lock 2024-03-18 18:42:24.000000000 +0000 @@ -666,9 +666,9 @@ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -739,9 +739,9 @@ [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -771,9 +771,9 @@ [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" dependencies = [ "cc", "libc", @@ -908,7 +908,7 @@ [[package]] name = "pushpin" -version = "1.38.0-dev" +version = "1.39.0-dev" dependencies = [ "arrayvec", "base64 0.13.1", @@ -1516,9 +1516,9 @@ [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1526,9 +1526,9 @@ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -1541,9 +1541,9 @@ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1551,9 +1551,9 @@ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -1564,9 +1564,9 @@ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" diff -Nru pushpin-1.38.0/Cargo.toml pushpin-1.39.1/Cargo.toml --- pushpin-1.38.0/Cargo.toml 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/Cargo.toml 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ [package] name = "pushpin" -version = "1.38.0-dev" +version = "1.39.0-dev" authors = ["Justin Karneges "] description = "Reverse proxy for realtime web services" repository = "https://github.com/fastly/pushpin" @@ -22,6 +22,7 @@ arrayvec = "0.7" base64 = "0.13" clap = { version = "=4.2.1", features = ["cargo", "string", "wrap_help", "derive"] } +config = "0.13.3" httparse = "1.7" ipnet = "2" jsonwebtoken = "8" @@ -43,7 +44,6 @@ time = { version = "=0.3.18", features = ["formatting", "local-offset", "macros"] } url = "2.3" zmq = "0.9" -config = "0.13.3" [dev-dependencies] criterion = "0.5" diff -Nru pushpin-1.38.0/build.rs pushpin-1.39.1/build.rs --- pushpin-1.38.0/build.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/build.rs 2024-03-18 18:42:24.000000000 +0000 @@ -1,12 +1,14 @@ use std::collections::HashMap; +use std::env; use std::error::Error; use std::ffi::OsStr; +use std::fmt; use std::fs::{self, File}; -use std::io::{BufRead, Write}; +use std::io::{self, BufRead, Write}; +use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; -use std::process::Command; -use std::thread; -use std::{env, io}; +use std::process::{Command, ExitStatus, Output, Stdio}; +use std::str::FromStr; use time::macros::format_description; use time::OffsetDateTime; @@ -26,27 +28,57 @@ version } +#[derive(Clone)] +struct LibVersion { + maj: u16, + min: u16, + orig: String, +} + +#[derive(Debug)] +struct ParseVersionError; + +impl fmt::Display for ParseVersionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Ok(write!(f, "failed to parse version")?) + } +} + +impl Error for ParseVersionError {} + +impl FromStr for LibVersion { + type Err = ParseVersionError; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split('.').collect(); + + if parts.len() < 2 { + return Err(ParseVersionError); + } + + let (maj, min): (u16, u16) = match (parts[0].parse(), parts[1].parse()) { + (Ok(maj), Ok(min)) => (maj, min), + _ => return Err(ParseVersionError), + }; + + Ok(LibVersion { + maj, + min, + orig: s.to_string(), + }) + } +} + fn check_version( pkg: &str, - found: &str, + found: LibVersion, expect_maj: u16, expect_min: u16, ) -> Result<(), Box> { - let parts: Vec<&str> = found.split('.').collect(); - - if parts.len() < 2 { - return Err(format!("unexpected {} version string: {}", pkg, found).into()); - } - - let (maj, min): (u16, u16) = match (parts[0].parse(), parts[1].parse()) { - (Ok(maj), Ok(min)) => (maj, min), - _ => return Err(format!("unexpected {} version string: {}", pkg, found).into()), - }; - - if maj < expect_maj || (maj == expect_maj && min < expect_min) { + if found.maj < expect_maj || (found.maj == expect_maj && found.min < expect_min) { return Err(format!( "{} version >={}.{} required, found: {}", - pkg, expect_maj, expect_min, found + pkg, expect_maj, expect_min, found.orig, ) .into()); } @@ -77,6 +109,7 @@ dest: &Path, release: bool, include_paths: &[&Path], + deny_warnings: bool, ) -> Result<(), Box> { let mut f = fs::File::create(dest)?; @@ -94,6 +127,12 @@ writeln!(&mut f, "INCLUDEPATH += {}", path.display())?; } + writeln!(&mut f)?; + + if deny_warnings { + writeln!(&mut f, "QMAKE_CXXFLAGS += \"-Werror\"")?; + } + Ok(()) } @@ -116,40 +155,80 @@ Ok(()) } -fn get_boost_path() -> Result> { - let possible_paths = vec!["/usr/local/include", "/usr/include"]; - let boost_version = "boost/version.hpp"; +// returned vec size guaranteed >= 1 +fn get_args_lossy(command: &mut Command) -> Vec { + let mut args = vec![command.get_program().to_string_lossy().into_owned()]; - for path in possible_paths { - let path = PathBuf::from(path); - let full_path = path.join(boost_version); - if full_path.exists() { - let file = File::open(full_path)?; - let reader = io::BufReader::new(file); - - for line in reader.lines() { - match line { - Ok(x) => { - if x.contains("#define BOOST_LIB_VERSION") { - let parts: Vec<&str> = x.split('"').collect(); - if parts.len() >= 2 { - let version = parts[1].replace('_', "."); - check_version("boost", &version, 1, 71)?; - } else { - return Err("Error finding boost package verion".into()); - } - } - } - Err(_) => { - return Err("Error finding boost package verion".into()); - } - }; - } - return Ok(path); - } + for s in command.get_args() { + args.push(s.to_string_lossy().into_owned()); + } + + args +} + +// convert Result to Result, separating stdout +fn take_stdout(result: io::Result) -> (io::Result, Vec) { + match result { + Ok(output) => (Ok(output.status), output.stdout), + Err(e) => (Err(e), Vec::new()), + } +} + +fn check_command_result( + program: &str, + result: io::Result, +) -> Result<(), Box> { + let status = match result { + Ok(status) => status, + Err(e) => return Err(format!("{} failed: {}", program, e).into()), + }; + + if !status.success() { + return Err(format!("{} failed, {}", program, status).into()); } - Err("No boost package found".into()) + Ok(()) +} + +fn check_command(command: &mut Command) -> Result<(), Box> { + let args = get_args_lossy(command); + + println!("{}", args.join(" ")); + + check_command_result(&args[0], command.status()) +} + +fn check_command_capture_stdout(command: &mut Command) -> Result, Box> { + let args = get_args_lossy(command); + + println!("{}", args.join(" ")); + + // don't capture stderr + let command = command.stderr(Stdio::inherit()); + + let (result, output) = take_stdout(command.output()); + check_command_result(&args[0], result)?; + + Ok(output) +} + +fn check_qmake(qmake_path: &Path) -> Result> { + let version: LibVersion = { + let output = + check_command_capture_stdout(Command::new(qmake_path).args(["-query", "QT_VERSION"]))?; + + let s = String::from_utf8(output)?; + let s = s.trim(); + + match s.parse() { + Ok(v) => v, + Err(_) => return Err(format!("unexpected qt version string: [{}]", s).into()), + } + }; + + check_version("qt", version.clone(), 5, 12)?; + + Ok(version) } fn find_in_path(name: &str) -> Option { @@ -167,57 +246,182 @@ None } -fn main() -> Result<(), Box> { - // for qt 6, check for qmake in path. for previous versions, use pkg-config - let qmake_path = match find_in_path("qmake") { - Some(p) => p, - None => { - let qt_host_bins = { - let pkg = "Qt5Core"; - - let host_bins = pkg_config::get_variable(pkg, "host_bins")?; - - if host_bins.is_empty() { - return Err(format!( - "qmake must be in PATH or pkg-config variable host_bins must exist for {}", - pkg - ) - .into()); - } +fn find_qmake() -> Result<(PathBuf, LibVersion), Box> { + let mut errors = Vec::new(); + + // check for a usable qmake in PATH - PathBuf::from(host_bins) - }; + let names = &["qmake", "qmake6", "qmake5"]; - fs::canonicalize(qt_host_bins.join("qmake")) - .map_err(|_| format!("qmake not found in {}", qt_host_bins.display()))? + for name in names { + if let Some(p) = find_in_path(name) { + match check_qmake(&p) { + Ok(version) => return Ok((p, version)), + Err(e) => errors.push(format!("skipping {}: {}", p.display(), e)), + } } - }; + } + + if errors.is_empty() { + errors.push(format!("none of ({}) found in PATH", names.join(", "))); + } + + // check pkg-config - let qt_version = { - let output = Command::new(&qmake_path) - .args(["-query", "QT_VERSION"]) - .output()?; - assert!(output.status.success()); + let pkg = "Qt5Core"; - String::from_utf8(output.stdout)?.trim().to_string() + match pkg_config::get_variable(pkg, "host_bins") { + Ok(host_bins) if !host_bins.is_empty() => { + let host_bins = PathBuf::from(host_bins); + + match fs::canonicalize(host_bins.join("qmake")) { + Ok(p) => match check_qmake(&p) { + Ok(version) => return Ok((p, version)), + Err(e) => errors.push(format!("skipping {}: {}", p.display(), e)), + }, + Err(e) => errors.push(format!("qmake not found in {}: {}", host_bins.display(), e)), + } + } + Ok(_) => errors.push(format!( + "pkg-config variable host_bins does not exist for {}", + pkg + )), + Err(e) => errors.push(format!("pkg-config error for {}: {}", pkg, e)), + } + + Err(format!("unable to find a usable qmake: {}", errors.join(", ")).into()) +} + +fn get_qmake() -> Result<(PathBuf, LibVersion), Box> { + match env::var("QMAKE") { + Ok(s) => { + let path = PathBuf::from(s); + let version = check_qmake(&path)?; + + Ok((path, version)) + } + Err(env::VarError::NotPresent) => find_qmake(), + Err(env::VarError::NotUnicode(_)) => Err("QMAKE not unicode".into()), + } +} + +fn contains_file_prefix(dir: &Path, prefix: &str) -> Result { + for entry in fs::read_dir(dir)? { + let entry = entry?; + + if entry.file_name().as_bytes().starts_with(prefix.as_bytes()) { + return Ok(true); + } + } + + Ok(false) +} + +fn get_qt_lib_prefix(lib_dir: &Path, version_maj: u16) -> Result> { + let prefixes = if cfg!(target_os = "macos") { + [format!("Qt{}", version_maj), "Qt".to_string()] + } else { + [format!("libQt{}", version_maj), "libQt".to_string()] }; - check_version("qt", &qt_version, 5, 12)?; + for prefix in &prefixes { + if contains_file_prefix(lib_dir, prefix)? { + return Ok(prefix.strip_prefix("lib").unwrap_or(prefix).to_string()); + } + } + + Err(format!( + "no files in {} beginning with any of: {}", + lib_dir.display(), + prefixes.join(", ") + ) + .into()) +} + +fn find_boost_include_dir() -> Result> { + let paths = ["/usr/local/include", "/usr/include"]; + let version_filename = "boost/version.hpp"; + + for path in paths { + let path = PathBuf::from(path); + let full_path = path.join(version_filename); + + if !full_path.exists() { + continue; + } + + let file = File::open(&full_path)?; + let reader = io::BufReader::new(file); + + let mut version_line = None; + + for line in reader.lines() { + match line { + Ok(s) if s.contains("#define BOOST_LIB_VERSION") => version_line = Some(s), + Ok(_) => continue, + Err(e) => { + return Err(format!("failed to read {}: {}", full_path.display(), e).into()) + } + } + } - let boost_path = get_boost_path()?; + let version_line = match version_line { + Some(s) => s, + None => return Err(format!("version line not found in {}", full_path.display()).into()), + }; + + let parts: Vec<&str> = version_line.split('"').collect(); + + if parts.len() < 2 { + return Err(format!("failed to parse version line in {}", full_path.display()).into()); + } + + let version = parts[1].replace('_', "."); + + let version = match version.parse() { + Ok(v) => v, + Err(_) => return Err(format!("unexpected boost version string: {}", version).into()), + }; + + check_version("boost", version, 1, 71)?; + + return Ok(path); + } + + Err(format!( + "{} not found in any of: {}", + version_filename, + paths.join(", ") + ) + .into()) +} + +fn contains_subslice(haystack: &[T], needle: &[T]) -> bool { + haystack.windows(needle.len()).any(|w| w == needle) +} + +fn main() -> Result<(), Box> { + let (qmake_path, qt_version) = get_qmake()?; let qt_install_libs = { - let output = Command::new(&qmake_path) - .args(["-query", "QT_INSTALL_LIBS"]) - .output()?; - assert!(output.status.success()); + let output = check_command_capture_stdout( + Command::new(&qmake_path).args(["-query", "QT_INSTALL_LIBS"]), + )?; - let libs_dir = PathBuf::from(String::from_utf8(output.stdout)?.trim()); + let libs_dir = PathBuf::from(String::from_utf8(output)?.trim()); fs::canonicalize(&libs_dir) .map_err(|_| format!("QT_INSTALL_LIBS dir {} not found", libs_dir.display()))? }; + let qt_lib_prefix = get_qt_lib_prefix(&qt_install_libs, qt_version.maj)?; + + let boost_include_dir = match env::var("BOOST_INCLUDE_DIR") { + Ok(s) => PathBuf::from(s), + Err(env::VarError::NotPresent) => find_boost_include_dir()?, + Err(env::VarError::NotUnicode(_)) => return Err("BOOST_INCLUDE_DIR not unicode".into()), + }; + let default_vars = { let prefix = match env::var("PREFIX") { Ok(s) => Some(s), @@ -253,14 +457,27 @@ include_paths.push(out_dir.as_ref()); - if boost_path != Path::new("/usr/include") { - include_paths.push(boost_path.as_ref()); + if boost_include_dir != Path::new("/usr/include") { + include_paths.push(boost_include_dir.as_ref()); } + let deny_warnings = match env::var("CARGO_ENCODED_RUSTFLAGS") { + Ok(s) => { + let flags: Vec<&str> = s.split('\x1f').collect(); + + contains_subslice(&flags, &["-D", "warnings"]) + } + Err(env::VarError::NotPresent) => false, + Err(env::VarError::NotUnicode(_)) => { + return Err("CARGO_ENCODED_RUSTFLAGS not unicode".into()) + } + }; + write_cpp_conf_pri( &out_dir.join("conf.pri"), profile == "release", &include_paths, + deny_warnings, )?; write_postbuild_conf_pri( @@ -272,60 +489,54 @@ &log_dir, )?; - assert!(Command::new(&qmake_path) - .args([ - OsStr::new("-o"), - out_dir.join("Makefile").as_os_str(), - cpp_src_dir.join("cpp.pro").as_os_str(), - ]) - .status()? - .success()); - - assert!(Command::new(&qmake_path) - .args([ - OsStr::new("-o"), - out_dir.join("Makefile.test").as_os_str(), - cpp_tests_src_dir.join("tests.pro").as_os_str(), - ]) - .status()? - .success()); - - assert!(Command::new(&qmake_path) - .args(["-o", "Makefile", "postbuild.pro"]) - .current_dir("postbuild") - .status()? - .success()); - - let proc_count = thread::available_parallelism().map_or(1, |x| x.get()); - - assert!(Command::new("make") - .args(["-f", "Makefile"]) - .args(["-j", &proc_count.to_string()]) - .current_dir(&out_dir) - .status()? - .success()); - - assert!(Command::new("make") - .args(["-f", "Makefile.test"]) - .args(["-j", &proc_count.to_string()]) - .current_dir(&out_dir) - .status()? - .success()); + check_command(Command::new(&qmake_path).args([ + OsStr::new("-o"), + out_dir.join("Makefile").as_os_str(), + cpp_src_dir.join("cpp.pro").as_os_str(), + ]))?; + + check_command(Command::new(&qmake_path).args([ + OsStr::new("-o"), + out_dir.join("Makefile.test").as_os_str(), + cpp_tests_src_dir.join("tests.pro").as_os_str(), + ]))?; + + check_command( + Command::new(&qmake_path) + .args(["-o", "Makefile", "postbuild.pro"]) + .current_dir("postbuild"), + )?; + + check_command( + Command::new("make") + .env("MAKEFLAGS", env::var("CARGO_MAKEFLAGS")?) + .args(["-f", "Makefile"]) + .current_dir(&out_dir), + )?; + + check_command( + Command::new("make") + .env("MAKEFLAGS", env::var("CARGO_MAKEFLAGS")?) + .args(["-f", "Makefile.test"]) + .current_dir(&out_dir), + )?; println!("cargo:rustc-env=APP_VERSION={}", get_version()); println!("cargo:rustc-env=CONFIG_DIR={}/pushpin", config_dir); println!("cargo:rustc-env=LIB_DIR={}/pushpin", lib_dir); - println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rustc-cfg=qt_lib_prefix=\"{}\"", qt_lib_prefix); - #[cfg(target_os = "macos")] - println!( - "cargo:rustc-link-search=framework={}", - qt_install_libs.display() - ); + println!("cargo:rustc-link-search={}", out_dir.display()); - #[cfg(not(target_os = "macos"))] - println!("cargo:rustc-link-search={}", qt_install_libs.display()); + if cfg!(target_os = "macos") { + println!( + "cargo:rustc-link-search=framework={}", + qt_install_libs.display() + ); + } else { + println!("cargo:rustc-link-search={}", qt_install_libs.display()); + } println!("cargo:rerun-if-env-changed=RELEASE"); println!("cargo:rerun-if-env-changed=PREFIX"); diff -Nru pushpin-1.38.0/debian/changelog pushpin-1.39.1/debian/changelog --- pushpin-1.38.0/debian/changelog 2024-04-01 07:15:33.000000000 +0000 +++ pushpin-1.39.1/debian/changelog 2024-03-19 14:12:48.000000000 +0000 @@ -1,20 +1,14 @@ -pushpin (1.38.0-4build3) noble; urgency=medium +pushpin (1.39.1-1) unstable; urgency=medium - * No-change rebuild for CVE-2024-3094 + * New upstream version - -- William Grant Mon, 01 Apr 2024 18:15:33 +1100 + -- Jan Niehusmann Tue, 19 Mar 2024 15:12:48 +0100 -pushpin (1.38.0-4build2) noble; urgency=medium +pushpin (1.39.0-1) unstable; urgency=medium - * No-change rebuild against libqt5core5t64 + * New upstream version - -- Steve Langasek Fri, 15 Mar 2024 06:34:14 +0000 - -pushpin (1.38.0-4build1) noble; urgency=medium - - * No-change rebuild against libssl3t64 - - -- Steve Langasek Mon, 04 Mar 2024 21:06:15 +0000 + -- Jan Niehusmann Sat, 16 Mar 2024 12:50:54 +0100 pushpin (1.38.0-4) unstable; urgency=medium diff -Nru pushpin-1.38.0/debian/control pushpin-1.39.1/debian/control --- pushpin-1.38.0/debian/control 2024-03-04 21:06:15.000000000 +0000 +++ pushpin-1.39.1/debian/control 2024-03-19 14:12:48.000000000 +0000 @@ -1,8 +1,7 @@ Source: pushpin Section: net Priority: optional -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Jan Niehusmann +Maintainer: Jan Niehusmann Build-Depends: debhelper (>= 11), qtbase5-dev, qt5-qmake, qtchooser, libzmq3-dev (>= 2.0), pkg-config, qconf, cargo (>= 0.43), librust-clap-dev, librust-log-dev, librust-serde-dev, librust-serde-json-dev, librust-zmq-dev (>= 0.9.2-3), librust-serde-derive-dev, librust-base64-dev, librust-slab-dev, librust-jsonwebtoken-dev, librust-config-dev, librust-httparse-dev, librust-ipnet-dev, librust-miniz-oxide-dev, librust-mio-dev, librust-openssl-dev, librust-paste-dev, librust-rustls-dev, librust-rustls-native-certs-dev, librust-sha1-dev, librust-signal-hook-dev, librust-socket2-dev, librust-criterion-dev, libboost-dev, librust-url-dev, help2man Standards-Version: 4.1.4 Homepage: https://github.com/fanout/pushpin diff -Nru pushpin-1.38.0/debian/patches/debian-changes pushpin-1.39.1/debian/patches/debian-changes --- pushpin-1.38.0/debian/patches/debian-changes 2024-02-18 21:18:38.000000000 +0000 +++ pushpin-1.39.1/debian/patches/debian-changes 2024-03-19 14:12:48.000000000 +0000 @@ -4,24 +4,26 @@ Option single-debian-patch is used as the changes are tracked in git. ---- pushpin-1.38.0.orig/Cargo.toml -+++ pushpin-1.38.0/Cargo.toml +--- pushpin-1.39.1.orig/Cargo.toml ++++ pushpin-1.39.1/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pushpin" --version = "1.38.0-dev" -+version = "1.38.0" +-version = "1.39.0-dev" ++version = "1.39.0" authors = ["Justin Karneges "] description = "Reverse proxy for realtime web services" repository = "https://github.com/fastly/pushpin" -@@ -20,14 +20,14 @@ crate-type = ["rlib", "staticlib"] +@@ -20,15 +20,15 @@ crate-type = ["rlib", "staticlib"] [dependencies] arrayvec = "0.7" -base64 = "0.13" -clap = { version = "=4.2.1", features = ["cargo", "string", "wrap_help", "derive"] } +-config = "0.13.3" +base64 = "0.21" +clap = { version = "4", features = ["cargo", "string", "wrap_help", "derive"] } ++config = "0.11" httparse = "1.7" ipnet = "2" jsonwebtoken = "8" @@ -32,7 +34,7 @@ mio = { version = "0.8", features = ["os-poll", "os-ext", "net"] } openssl = "0.10" paste = "1.0" -@@ -38,21 +38,21 @@ serde_json = "1.0" +@@ -39,20 +39,20 @@ serde_json = "1.0" sha1 = "0.10" signal-hook = "0.3" slab = "0.4" @@ -42,8 +44,6 @@ -time = { version = "=0.3.18", features = ["formatting", "local-offset", "macros"] } -url = "2.3" zmq = "0.9" --config = "0.13.3" -+config = "0.11" +time = { version = "0.3", features = ["formatting", "local-offset", "macros"] } +url = "2.4" @@ -61,8 +61,8 @@ [[bench]] name = "server" ---- pushpin-1.38.0.orig/Makefile -+++ pushpin-1.38.0/Makefile +--- pushpin-1.39.1.orig/Makefile ++++ pushpin-1.39.1/Makefile @@ -25,7 +25,7 @@ postbuild-clean: FORCE cd postbuild && $(MAKE) -f Makefile clean @@ -72,9 +72,9 @@ check: cargo-test ---- pushpin-1.38.0.orig/examples/config/pushpin.conf -+++ pushpin-1.38.0/examples/config/pushpin.conf -@@ -109,7 +109,11 @@ sockjs_url=http://cdn.jsdelivr.net/sockj +--- pushpin-1.39.1.orig/examples/config/pushpin.conf ++++ pushpin-1.39.1/examples/config/pushpin.conf +@@ -108,7 +108,11 @@ sockjs_url=http://cdn.jsdelivr.net/sockj # pushpin will output a log message when a new version is available. report # mode helps the pushpin project build credibility, so please enable it if you # enjoy this software :) @@ -87,8 +87,8 @@ # use this field to identify your organization in updates requests. if left # blank, updates requests will be anonymous ---- pushpin-1.38.0.orig/src/client.rs -+++ pushpin-1.38.0/src/client.rs +--- pushpin-1.39.1.orig/src/client.rs ++++ pushpin-1.39.1/src/client.rs @@ -2564,7 +2564,6 @@ pub mod tests { use crate::connection::calculate_ws_accept; use crate::websocket; @@ -97,8 +97,8 @@ fn recv_frame( stream: &mut R, ---- pushpin-1.38.0.orig/src/connection.rs -+++ pushpin-1.38.0/src/connection.rs +--- pushpin-1.39.1.orig/src/connection.rs ++++ pushpin-1.39.1/src/connection.rs @@ -61,6 +61,7 @@ use crate::zhttppacket; use crate::zmq::MultipartHeader; use crate::{pin, Defer}; @@ -133,9 +133,9 @@ #[test] fn ws_ext_header() { ---- pushpin-1.38.0.orig/src/cpp/proxy/app.cpp -+++ pushpin-1.38.0/src/cpp/proxy/app.cpp -@@ -309,7 +309,7 @@ public: +--- pushpin-1.39.1.orig/src/cpp/proxy/app.cpp ++++ pushpin-1.39.1/src/cpp/proxy/app.cpp +@@ -486,7 +486,7 @@ public: Jwt::EncodingKey sigKey = Jwt::EncodingKey::fromConfigString(settings.value("proxy/sig_key").toString(), configDir); Jwt::DecodingKey upstreamKey = Jwt::DecodingKey::fromConfigString(settings.value("proxy/upstream_key").toString(), configDir); QString sockJsUrl = settings.value("proxy/sockjs_url").toString(); @@ -144,8 +144,8 @@ QString organizationName = settings.value("proxy/organization_name").toString(); int clientMaxconn = settings.value("runner/client_maxconn", 50000).toInt(); bool statsConnectionSend = settings.value("global/stats_connection_send", true).toBool(); ---- pushpin-1.38.0.orig/src/internal.conf -+++ pushpin-1.38.0/src/internal.conf +--- pushpin-1.39.1.orig/src/internal.conf ++++ pushpin-1.39.1/src/internal.conf @@ -27,13 +27,13 @@ condure_client_out_stream_specs=ipc://{r condure_client_in_specs=ipc://{rundir}/{ipc_prefix}condure-client-out @@ -163,8 +163,8 @@ # bind DEALER for requesting inspection info (internal, used with handler) handler_inspect_spec=ipc://{rundir}/{ipc_prefix}inspect ---- pushpin-1.38.0.orig/src/lib.rs -+++ pushpin-1.38.0/src/lib.rs +--- pushpin-1.39.1.orig/src/lib.rs ++++ pushpin-1.39.1/src/lib.rs @@ -26,7 +26,7 @@ pub mod buffer; pub mod channel; pub mod client; @@ -186,26 +186,8 @@ pub mod shuffle; pub mod timer; pub mod tls; -@@ -170,7 +170,7 @@ fn get_user_uid(name: &str) -> Result
  • Result( stream: &mut R, ---- pushpin-1.38.0.orig/src/zhttpsocket.rs -+++ pushpin-1.38.0/src/zhttpsocket.rs -@@ -2653,7 +2653,6 @@ mod tests { +--- pushpin-1.39.1.orig/src/zhttpsocket.rs ++++ pushpin-1.39.1/src/zhttpsocket.rs +@@ -2740,7 +2740,6 @@ mod tests { use crate::zhttppacket::{ PacketParse, Request, RequestData, RequestPacket, Response, ResponsePacket, }; diff -Nru pushpin-1.38.0/examples/config/pushpin.conf pushpin-1.39.1/examples/config/pushpin.conf --- pushpin-1.38.0/examples/config/pushpin.conf 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/examples/config/pushpin.conf 2024-03-18 18:42:24.000000000 +0000 @@ -46,7 +46,6 @@ allow_compression=false # paths -condure_bin=condure mongrel2_bin=mongrel2 m2sh_bin=m2sh zurl_bin=zurl diff -Nru pushpin-1.38.0/postbuild/postbuild.pro pushpin-1.39.1/postbuild/postbuild.pro --- pushpin-1.38.0/postbuild/postbuild.pro 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/postbuild/postbuild.pro 2024-03-18 18:42:24.000000000 +0000 @@ -59,7 +59,7 @@ pushpin_conf_inst.target = pushpin.conf.inst pushpin_conf_inst.commands = sed -e \"s,configdir=.*,configdir=$$CONFIGDIR/runner,g\" -e \"s,rundir=.*,rundir=$$RUNDIR,g\" -e \"s,logdir=.*,logdir=$$LOGDIR,g\" ../examples/config/pushpin.conf > pushpin.conf.inst -pushpin_conf_inst.depends = ../examples/config/pushpin.conf +pushpin_conf_inst.depends = ../examples/config/pushpin.conf conf.pri QMAKE_EXTRA_TARGETS += pushpin_conf_inst PRE_TARGETDEPS += pushpin.conf.inst diff -Nru pushpin-1.38.0/src/arena.rs pushpin-1.39.1/src/arena.rs --- pushpin-1.38.0/src/arena.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/arena.rs 2024-03-18 18:42:24.000000000 +0000 @@ -478,7 +478,7 @@ } pub struct ReusableVec { - vec: Vec<()>, + vec: Vec, size: usize, align: usize, } @@ -492,7 +492,7 @@ // safety: we must cast to Vec before using, where U has the same // size and alignment as T - let vec: Vec<()> = unsafe { mem::transmute(vec) }; + let vec: Vec = unsafe { mem::transmute(vec) }; Self { vec, size, align } } @@ -506,7 +506,7 @@ assert_eq!(self.size, size); assert_eq!(self.align, align); - let vec: &mut Vec<()> = &mut self.vec; + let vec: &mut Vec = &mut self.vec; // safety: U has the expected size and alignment let vec: &mut Vec = unsafe { mem::transmute(vec) }; diff -Nru pushpin-1.38.0/src/bin/m2adapter.rs pushpin-1.39.1/src/bin/m2adapter.rs --- pushpin-1.38.0/src/bin/m2adapter.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/bin/m2adapter.rs 2024-03-18 18:42:24.000000000 +0000 @@ -14,25 +14,11 @@ * limitations under the License. */ -use pushpin::call_c_main; +use pushpin::{call_c_main, import_cpp}; use std::env; use std::process::ExitCode; -#[cfg(target_os = "macos")] -#[link(name = "pushpin-cpp")] -#[link(name = "QtCore", kind = "framework")] -#[link(name = "QtNetwork", kind = "framework")] -#[link(name = "c++")] -extern "C" { - fn m2adapter_main(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; -} - -#[cfg(not(target_os = "macos"))] -#[link(name = "pushpin-cpp")] -#[link(name = "Qt5Core")] -#[link(name = "Qt5Network")] -#[link(name = "stdc++")] -extern "C" { +import_cpp! { fn m2adapter_main(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; } diff -Nru pushpin-1.38.0/src/bin/pushpin-handler.rs pushpin-1.39.1/src/bin/pushpin-handler.rs --- pushpin-1.38.0/src/bin/pushpin-handler.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/bin/pushpin-handler.rs 2024-03-18 18:42:24.000000000 +0000 @@ -14,25 +14,11 @@ * limitations under the License. */ -use pushpin::call_c_main; +use pushpin::{call_c_main, import_cpp}; use std::env; use std::process::ExitCode; -#[cfg(target_os = "macos")] -#[link(name = "pushpin-cpp")] -#[link(name = "QtCore", kind = "framework")] -#[link(name = "QtNetwork", kind = "framework")] -#[link(name = "c++")] -extern "C" { - fn handler_main(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; -} - -#[cfg(not(target_os = "macos"))] -#[link(name = "pushpin-cpp")] -#[link(name = "Qt5Core")] -#[link(name = "Qt5Network")] -#[link(name = "stdc++")] -extern "C" { +import_cpp! { fn handler_main(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; } diff -Nru pushpin-1.38.0/src/bin/pushpin-proxy.rs pushpin-1.39.1/src/bin/pushpin-proxy.rs --- pushpin-1.38.0/src/bin/pushpin-proxy.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/bin/pushpin-proxy.rs 2024-03-18 18:42:24.000000000 +0000 @@ -14,25 +14,11 @@ * limitations under the License. */ -use pushpin::call_c_main; +use pushpin::{call_c_main, import_cpp}; use std::env; use std::process::ExitCode; -#[cfg(target_os = "macos")] -#[link(name = "pushpin-cpp")] -#[link(name = "QtCore", kind = "framework")] -#[link(name = "QtNetwork", kind = "framework")] -#[link(name = "c++")] -extern "C" { - fn proxy_main(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; -} - -#[cfg(not(target_os = "macos"))] -#[link(name = "pushpin-cpp")] -#[link(name = "Qt5Core")] -#[link(name = "Qt5Network")] -#[link(name = "stdc++")] -extern "C" { +import_cpp! { fn proxy_main(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; } diff -Nru pushpin-1.38.0/src/bin/pushpin.rs pushpin-1.39.1/src/bin/pushpin.rs --- pushpin-1.38.0/src/bin/pushpin.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/bin/pushpin.rs 2024-03-18 18:42:24.000000000 +0000 @@ -14,25 +14,11 @@ * limitations under the License. */ -use pushpin::call_c_main; +use pushpin::{call_c_main, import_cpp}; use std::env; use std::process::ExitCode; -#[cfg(target_os = "macos")] -#[link(name = "pushpin-cpp")] -#[link(name = "QtCore", kind = "framework")] -#[link(name = "QtNetwork", kind = "framework")] -#[link(name = "c++")] -extern "C" { - fn runner_main(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; -} - -#[cfg(not(target_os = "macos"))] -#[link(name = "pushpin-cpp")] -#[link(name = "Qt5Core")] -#[link(name = "Qt5Network")] -#[link(name = "stdc++")] -extern "C" { +import_cpp! { fn runner_main(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; } diff -Nru pushpin-1.38.0/src/cpp/config.cpp pushpin-1.39.1/src/cpp/config.cpp --- pushpin-1.38.0/src/cpp/config.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/config.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -26,7 +26,7 @@ namespace Config { -static Config *g_config = 0; +static thread_local Config *g_config = 0; Config & get() { diff -Nru pushpin-1.38.0/src/cpp/config.h pushpin-1.39.1/src/cpp/config.h --- pushpin-1.38.0/src/cpp/config.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/config.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -35,6 +35,7 @@ QString libDir; }; +// value is thread local Config & get(); } diff -Nru pushpin-1.38.0/src/cpp/cpp.pro pushpin-1.39.1/src/cpp/cpp.pro --- pushpin-1.38.0/src/cpp/cpp.pro 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/cpp.pro 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ TEMPLATE = lib CONFIG -= app_bundle -CONFIG += staticlib c++11 +CONFIG += staticlib c++14 QT -= gui QT += network TARGET = pushpin-cpp diff -Nru pushpin-1.38.0/src/cpp/handler/conncheckworker.cpp pushpin-1.39.1/src/cpp/handler/conncheckworker.cpp --- pushpin-1.38.0/src/cpp/handler/conncheckworker.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/conncheckworker.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,6 +23,7 @@ #include "conncheckworker.h" +#include "qtcompat.h" #include "zrpcrequest.h" #include "controlrequest.h" #include "statsmanager.h" @@ -34,7 +36,7 @@ QVariantHash args = req_->args(); - if(!args.contains("ids") || args["ids"].type() != QVariant::List) + if(!args.contains("ids") || typeId(args["ids"]) != QMetaType::QVariantList) { respondError("bad-request"); return; @@ -44,7 +46,7 @@ foreach(const QVariant &vid, vids) { - if(vid.type() != QVariant::ByteArray) + if(typeId(vid) != QMetaType::QByteArray) { respondError("bad-request"); return; diff -Nru pushpin-1.38.0/src/cpp/handler/controlrequest.cpp pushpin-1.39.1/src/cpp/handler/controlrequest.cpp --- pushpin-1.38.0/src/cpp/handler/controlrequest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/controlrequest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-2017 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,6 +24,7 @@ #include "controlrequest.h" #include "packet/statspacket.h" +#include "qtcompat.h" #include "deferred.h" #include "zrpcrequest.h" @@ -56,7 +58,7 @@ if(req->success()) { QVariant vresult = req->result(); - if(vresult.type() != QVariant::List) + if(typeId(vresult) != QMetaType::QVariantList) { setFinished(false); return; @@ -67,7 +69,7 @@ CidSet out; foreach(const QVariant &vcid, result) { - if(vcid.type() != QVariant::ByteArray) + if(typeId(vcid) != QMetaType::QByteArray) { setFinished(false); return; diff -Nru pushpin-1.38.0/src/cpp/handler/handlerapp.cpp pushpin-1.39.1/src/cpp/handler/handlerapp.cpp --- pushpin-1.38.0/src/cpp/handler/handlerapp.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/handlerapp.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2015-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -49,6 +50,30 @@ } } +static QStringList expandSpecs(const QStringList &l, int peerCount) +{ + if(l.count() == 1 && l[0].startsWith("ipc:") && peerCount > 1) + { + QString base = l[0]; + + QStringList out; + for(int i = 0; i < peerCount; ++i) + out += base + QString("-%1").arg(i); + + return out; + } + + return l; +} + +static QString firstSpec(const QString &s, int peerCount) +{ + if(s.startsWith("ipc:") && peerCount > 1) + return s + "-0"; + + return s; +} + enum CommandLineParseResult { CommandLineOk, @@ -237,6 +262,7 @@ trimlist(&condure_in_stream_specs); QStringList condure_out_specs = settings.value("proxy/condure_out_specs").toStringList(); trimlist(&condure_out_specs); + int proxyWorkerCount = settings.value("proxy/workers", 1).toInt(); QStringList m2a_in_stream_specs = settings.value("handler/m2a_in_stream_specs").toStringList(); trimlist(&m2a_in_stream_specs); QStringList m2a_out_specs = settings.value("handler/m2a_out_specs").toStringList(); @@ -247,15 +273,33 @@ trimlist(&intreq_out_stream_specs); QStringList intreq_in_specs = settings.value("handler/proxy_intreq_in_specs").toStringList(); trimlist(&intreq_in_specs); + QStringList proxy_inspect_specs = settings.value("handler/proxy_inspect_specs").toStringList(); + trimlist(&proxy_inspect_specs); QString proxy_inspect_spec = settings.value("handler/proxy_inspect_spec").toString(); + if(!proxy_inspect_spec.isEmpty()) + proxy_inspect_specs += proxy_inspect_spec; + QStringList proxy_accept_specs = settings.value("handler/proxy_accept_specs").toStringList(); + trimlist(&proxy_accept_specs); QString proxy_accept_spec = settings.value("handler/proxy_accept_spec").toString(); + if(!proxy_accept_spec.isEmpty()) + proxy_accept_specs += proxy_accept_spec; + QStringList proxy_retry_out_specs = settings.value("handler/proxy_retry_out_specs").toStringList(); + trimlist(&proxy_retry_out_specs); QString proxy_retry_out_spec = settings.value("handler/proxy_retry_out_spec").toString(); - QString ws_control_in_spec = settings.value("handler/proxy_ws_control_in_spec").toString(); - QString ws_control_out_spec = settings.value("handler/proxy_ws_control_out_spec").toString(); + if(!proxy_retry_out_spec.isEmpty()) + proxy_retry_out_specs += proxy_retry_out_spec; + QStringList ws_control_init_specs = settings.value("handler/proxy_ws_control_init_specs").toStringList(); + trimlist(&ws_control_init_specs); + QStringList ws_control_stream_specs = settings.value("handler/proxy_ws_control_stream_specs").toStringList(); + trimlist(&ws_control_stream_specs); QString stats_spec = settings.value("handler/stats_spec").toString(); QString command_spec = settings.value("handler/command_spec").toString(); QString state_spec = settings.value("handler/state_spec").toString(); + QStringList proxy_stats_specs = settings.value("handler/proxy_stats_specs").toStringList(); + trimlist(&proxy_stats_specs); QString proxy_stats_spec = settings.value("handler/proxy_stats_spec").toString(); + if(!proxy_stats_spec.isEmpty()) + proxy_stats_specs += proxy_stats_spec; QString proxy_command_spec = settings.value("handler/proxy_command_spec").toString(); QString push_in_spec = settings.value("handler/push_in_spec").toString(); QStringList push_in_sub_specs = settings.value("handler/push_in_sub_specs").toStringList(); @@ -294,9 +338,9 @@ return; } - if(proxy_inspect_spec.isEmpty() || proxy_accept_spec.isEmpty() || proxy_retry_out_spec.isEmpty()) + if(proxy_inspect_specs.isEmpty() || proxy_accept_specs.isEmpty() || proxy_retry_out_specs.isEmpty()) { - log_error("must set proxy_inspect_spec, proxy_accept_spec, and proxy_retry_out_spec"); + log_error("must set proxy_inspect_specs, proxy_accept_specs, and proxy_retry_out_specs"); q->quit(0); return; } @@ -314,19 +358,19 @@ config.serverInStreamSpecs = m2a_in_stream_specs; config.serverOutSpecs = m2a_out_specs; } - config.clientOutSpecs = intreq_out_specs; - config.clientOutStreamSpecs = intreq_out_stream_specs; - config.clientInSpecs = intreq_in_specs; - config.inspectSpec = proxy_inspect_spec; - config.acceptSpec = proxy_accept_spec; - config.retryOutSpec = proxy_retry_out_spec; - config.wsControlInSpec = ws_control_in_spec; - config.wsControlOutSpec = ws_control_out_spec; + config.clientOutSpecs = expandSpecs(intreq_out_specs, proxyWorkerCount); + config.clientOutStreamSpecs = expandSpecs(intreq_out_stream_specs, proxyWorkerCount); + config.clientInSpecs = expandSpecs(intreq_in_specs, proxyWorkerCount); + config.inspectSpecs = expandSpecs(proxy_inspect_specs, proxyWorkerCount); + config.acceptSpecs = expandSpecs(proxy_accept_specs, proxyWorkerCount); + config.retryOutSpecs = expandSpecs(proxy_retry_out_specs, proxyWorkerCount); + config.wsControlInitSpecs = expandSpecs(ws_control_init_specs, proxyWorkerCount); + config.wsControlStreamSpecs = expandSpecs(ws_control_stream_specs, proxyWorkerCount); config.statsSpec = stats_spec; config.commandSpec = command_spec; config.stateSpec = state_spec; - config.proxyStatsSpec = proxy_stats_spec; - config.proxyCommandSpec = proxy_command_spec; + config.proxyStatsSpecs = expandSpecs(proxy_stats_specs, proxyWorkerCount); + config.proxyCommandSpec = firstSpec(proxy_command_spec, proxyWorkerCount); config.pushInSpec = push_in_spec; config.pushInSubSpecs = push_in_sub_specs; config.pushInSubConnect = push_in_sub_connect; diff -Nru pushpin-1.38.0/src/cpp/handler/handlerengine.cpp pushpin-1.39.1/src/cpp/handler/handlerengine.cpp --- pushpin-1.38.0/src/cpp/handler/handlerengine.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/handlerengine.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2015-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -33,6 +33,8 @@ #include #include "qzmqsocket.h" #include "qzmqvalve.h" +#include "qzmqreqmessage.h" +#include "qtcompat.h" #include "tnetstring.h" #include "rtimer.h" #include "log.h" @@ -138,7 +140,7 @@ { QVariantHash args = req->args(); - if(!args.contains("method") || args["method"].type() != QVariant::ByteArray) + if(!args.contains("method") || typeId(args["method"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -146,7 +148,7 @@ requestData.method = QString::fromLatin1(args["method"].toByteArray()); - if(!args.contains("uri") || args["uri"].type() != QVariant::ByteArray) + if(!args.contains("uri") || typeId(args["uri"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -159,7 +161,7 @@ return; } - if(!args.contains("headers") || args["headers"].type() != QVariant::List) + if(!args.contains("headers") || typeId(args["headers"]) != QMetaType::QVariantList) { respondError("bad-request"); return; @@ -167,14 +169,14 @@ foreach(const QVariant &vheader, args["headers"].toList()) { - if(vheader.type() != QVariant::List) + if(typeId(vheader) != QMetaType::QVariantList) { respondError("bad-request"); return; } QVariantList vlist = vheader.toList(); - if(vlist.count() != 2 || vlist[0].type() != QVariant::ByteArray || vlist[1].type() != QVariant::ByteArray) + if(vlist.count() != 2 || typeId(vlist[0]) != QMetaType::QByteArray || typeId(vlist[1]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -183,7 +185,7 @@ requestData.headers += HttpHeader(vlist[0].toByteArray(), vlist[1].toByteArray()); } - if(!args.contains("body") || args["body"].type() != QVariant::ByteArray) + if(!args.contains("body") || typeId(args["body"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -194,7 +196,7 @@ truncated = false; if(args.contains("truncated")) { - if(args["truncated"].type() != QVariant::Bool) + if(typeId(args["truncated"]) != QMetaType::Bool) { respondError("bad-request"); return; @@ -206,7 +208,7 @@ bool getSession = false; if(args.contains("get-session")) { - if(args["get-session"].type() != QVariant::Bool) + if(typeId(args["get-session"]) != QMetaType::Bool) { respondError("bad-request"); return; @@ -218,7 +220,7 @@ autoShare = false; if(args.contains("auto-share")) { - if(args["auto-share"].type() != QVariant::Bool) + if(typeId(args["auto-share"]) != QMetaType::Bool) { respondError("bad-request"); return; @@ -471,7 +473,7 @@ // process conn-max packets before doing anything else if(args.contains("conn-max")) { - if(args["conn-max"].type() != QVariant::List) + if(typeId(args["conn-max"]) != QMetaType::QVariantList) { respondError("bad-request"); return; @@ -494,7 +496,7 @@ if(args.contains("route")) { - if(args["route"].type() != QVariant::ByteArray) + if(typeId(args["route"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -505,7 +507,7 @@ if(args.contains("separate-stats")) { - if(args["separate-stats"].type() != QVariant::Bool) + if(typeId(args["separate-stats"]) != QMetaType::Bool) { respondError("bad-request"); return; @@ -519,7 +521,7 @@ if(args.contains("channel-prefix")) { - if(args["channel-prefix"].type() != QVariant::ByteArray) + if(typeId(args["channel-prefix"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -530,7 +532,7 @@ if(args.contains("channels")) { - if(args["channels"].type() != QVariant::List) + if(typeId(args["channels"]) != QMetaType::QVariantList) { respondError("bad-request"); return; @@ -539,7 +541,7 @@ QVariantList vchannels = args["channels"].toList(); foreach(const QVariant &v, vchannels) { - if(v.type() != QVariant::ByteArray) + if(typeId(v) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -551,7 +553,7 @@ if(args.contains("trusted")) { - if(args["trusted"].type() != QVariant::Bool) + if(typeId(args["trusted"]) != QMetaType::Bool) { respondError("bad-request"); return; @@ -562,7 +564,7 @@ // parse requests - if(!args.contains("requests") || args["requests"].type() != QVariant::List) + if(!args.contains("requests") || typeId(args["requests"]) != QMetaType::QVariantList) { respondError("bad-request"); return; @@ -600,7 +602,7 @@ // parse response - if(!args.contains("response") || args["response"].type() != QVariant::Hash) + if(!args.contains("response") || typeId(args["response"]) != QMetaType::QVariantHash) { respondError("bad-request"); return; @@ -608,7 +610,7 @@ QVariantHash rd = args["response"].toHash(); - if(!rd.contains("code") || !rd["code"].canConvert(QVariant::Int)) + if(!rd.contains("code") || !canConvert(rd["code"], QMetaType::Int)) { respondError("bad-request"); return; @@ -616,7 +618,7 @@ responseData.code = rd["code"].toInt(); - if(!rd.contains("reason") || rd["reason"].type() != QVariant::ByteArray) + if(!rd.contains("reason") || typeId(rd["reason"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -624,7 +626,7 @@ responseData.reason = rd["reason"].toByteArray(); - if(!rd.contains("headers") || rd["headers"].type() != QVariant::List) + if(!rd.contains("headers") || typeId(rd["headers"]) != QMetaType::QVariantList) { respondError("bad-request"); return; @@ -632,14 +634,14 @@ foreach(const QVariant &vheader, rd["headers"].toList()) { - if(vheader.type() != QVariant::List) + if(typeId(vheader) != QMetaType::QVariantList) { respondError("bad-request"); return; } QVariantList vlist = vheader.toList(); - if(vlist.count() != 2 || vlist[0].type() != QVariant::ByteArray || vlist[1].type() != QVariant::ByteArray) + if(vlist.count() != 2 || typeId(vlist[0]) != QMetaType::QByteArray || typeId(vlist[1]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -648,7 +650,7 @@ responseData.headers += HttpHeader(vlist[0].toByteArray(), vlist[1].toByteArray()); } - if(!rd.contains("body") || rd["body"].type() != QVariant::ByteArray) + if(!rd.contains("body") || typeId(rd["body"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -658,7 +660,7 @@ if(args.contains("inspect")) { - if(args["inspect"].type() != QVariant::Hash) + if(typeId(args["inspect"]) != QMetaType::QVariantHash) { respondError("bad-request"); return; @@ -666,7 +668,7 @@ QVariantHash vinspect = args["inspect"].toHash(); - if(!vinspect.contains("no-proxy") || vinspect["no-proxy"].type() != QVariant::Bool) + if(!vinspect.contains("no-proxy") || typeId(vinspect["no-proxy"]) != QMetaType::Bool) { respondError("bad-request"); return; @@ -677,7 +679,7 @@ inspectInfo.sharingKey.clear(); if(vinspect.contains("sharing-key")) { - if(vinspect["sharing-key"].type() != QVariant::ByteArray) + if(typeId(vinspect["sharing-key"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -688,7 +690,7 @@ if(vinspect.contains("sid")) { - if(vinspect["sid"].type() != QVariant::ByteArray) + if(typeId(vinspect["sid"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -699,7 +701,7 @@ if(vinspect.contains("last-ids")) { - if(vinspect["last-ids"].type() != QVariant::Hash) + if(typeId(vinspect["last-ids"]) != QMetaType::QVariantHash) { respondError("bad-request"); return; @@ -711,7 +713,7 @@ { it.next(); - if(it.value().type() != QVariant::ByteArray) + if(typeId(it.value()) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -730,7 +732,7 @@ if(args.contains("response-sent")) { - if(args["response-sent"].type() != QVariant::Bool) + if(typeId(args["response-sent"]) != QMetaType::Bool) { respondError("bad-request"); return; @@ -742,7 +744,7 @@ bool useSession = false; if(args.contains("use-session")) { - if(args["use-session"].type() != QVariant::Bool) + if(typeId(args["use-session"]) != QMetaType::Bool) { respondError("bad-request"); return; @@ -818,47 +820,46 @@ return out; } -signals: - void sessionsReady(); - void retryPacketReady(const RetryRequestPacket &packet); + Signal sessionsReady; + boost::signals2::signal retryPacketReady; private: static HttpRequestData parseRequestData(const QVariantHash &args, const QString &field) { - if(!args.contains(field) || args[field].type() != QVariant::Hash) + if(!args.contains(field) || typeId(args[field]) != QMetaType::QVariantHash) return HttpRequestData(); QVariantHash rd = args[field].toHash(); - if(!rd.contains("method") || rd["method"].type() != QVariant::ByteArray) + if(!rd.contains("method") || typeId(rd["method"]) != QMetaType::QByteArray) return HttpRequestData(); HttpRequestData out; out.method = QString::fromLatin1(rd["method"].toByteArray()); - if(!rd.contains("uri") || rd["uri"].type() != QVariant::ByteArray) + if(!rd.contains("uri") || typeId(rd["uri"]) != QMetaType::QByteArray) return HttpRequestData(); out.uri = QUrl(rd["uri"].toString(), QUrl::StrictMode); if(!out.uri.isValid()) return HttpRequestData(); - if(!rd.contains("headers") || rd["headers"].type() != QVariant::List) + if(!rd.contains("headers") || typeId(rd["headers"]) != QMetaType::QVariantList) return HttpRequestData(); foreach(const QVariant &vheader, rd["headers"].toList()) { - if(vheader.type() != QVariant::List) + if(typeId(vheader) != QMetaType::QVariantList) return HttpRequestData(); QVariantList vlist = vheader.toList(); - if(vlist.count() != 2 || vlist[0].type() != QVariant::ByteArray || vlist[1].type() != QVariant::ByteArray) + if(vlist.count() != 2 || typeId(vlist[0]) != QMetaType::QByteArray || typeId(vlist[1]) != QMetaType::QByteArray) return HttpRequestData(); out.headers += HttpHeader(vlist[0].toByteArray(), vlist[1].toByteArray()); } - if(!rd.contains("body") || rd["body"].type() != QVariant::ByteArray) + if(!rd.contains("body") || typeId(rd["body"]) != QMetaType::QByteArray) return HttpRequestData(); out.body = rd["body"].toByteArray(); @@ -958,11 +959,13 @@ return; } + QByteArray reqFrom = req->from(); + QVariantHash result; result["accepted"] = true; req->respond(result); - log_debug("accepting %d requests", requestStates.count()); + log_debug("accepting %d requests from %s", requestStates.count(), reqFrom.data()); if(instruct.holdMode == Instruct::ResponseHold) { @@ -996,7 +999,7 @@ needRemoveFromStats.remove(cid); - int unreportedTime = stats->removeConnection(cid, true); + int unreportedTime = stats->removeConnection(cid, true, reqFrom); RetryRequestPacket::Request rpreq; rpreq.rid = rs.rid; @@ -1045,9 +1048,9 @@ } rp.route = route.toUtf8(); - rp.retrySeq = stats->lastRetrySeq(); + rp.retrySeq = stats->lastRetrySeq(reqFrom); - emit retryPacketReady(rp); + retryPacketReady(reqFrom, rp); setFinished(true); return; @@ -1086,6 +1089,7 @@ implicitChannelsSet += channel; HttpSession::AcceptData adata; + adata.from = reqFrom; adata.requestData = origRequestData; adata.logicalPeerAddress = rs.logicalPeerAddress; adata.debug = rs.debug; @@ -1112,7 +1116,7 @@ // engine should directly connect to this and register the holds // immediately, to avoid a race with the lastId check - emit sessionsReady(); + sessionsReady(); setFinished(true); } @@ -1170,8 +1174,7 @@ timer_->start(SUBSCRIBED_DELAY); } -signals: - void subscribed(); + Signal subscribed; private: QString channel_; @@ -1180,7 +1183,7 @@ private slots: void timer_timeout() { - emit subscribed(); + subscribed(); } }; @@ -1215,6 +1218,12 @@ } }; + struct WSSessionConnections { + Connection sendConnection; + Connection expConnection; + Connection errorConnection; + }; + HandlerEngine *q; Configuration config; ZhttpManager *zhttpIn; @@ -1229,16 +1238,17 @@ QZmq::Socket *inSubSock; QZmq::Valve *inSubValve; QZmq::Socket *retrySock; - QZmq::Socket *wsControlInSock; - QZmq::Valve *wsControlInValve; - QZmq::Socket *wsControlOutSock; + QZmq::Socket *wsControlInitSock; + QZmq::Valve *wsControlInitValve; + QZmq::Socket *wsControlStreamSock; + QZmq::Valve *wsControlStreamValve; QZmq::Socket *statsSock; QZmq::Socket *proxyStatsSock; QZmq::Valve *proxyStatsValve; SimpleHttpServer *controlHttpServer; StatsManager *stats; - RateLimiter *publishLimiter; - RateLimiter *updateLimiter; + std::unique_ptr publishLimiter; + std::unique_ptr updateLimiter; HttpSessionUpdateManager *httpSessionUpdateManager; Sequencer *sequencer; CommonState cs; @@ -1252,6 +1262,18 @@ Connection controlServerConnection; Connection itemReadyConnection; map finishedConnection; + map subscribedConnection; + map retryPacketReadyConnection; + map sessionsReadyConnection; + Connection connectionsRefreshedConnection; + Connection unsubscribedConnection; + Connection reportedConnection; + map wsSessionConnectionMap; + Connection pullConnection; + Connection controlInitValveConnection; + Connection controlStreamValveConnection; + Connection inSubValveConnection; + Connection proxyStatConnection; Private(HandlerEngine *_q) : QObject(_q), @@ -1268,9 +1290,10 @@ inSubSock(0), inSubValve(0), retrySock(0), - wsControlInSock(0), - wsControlInValve(0), - wsControlOutSock(0), + wsControlInitSock(0), + wsControlInitValve(0), + wsControlStreamSock(0), + wsControlStreamValve(0), statsSock(0), proxyStatsSock(0), proxyStatsValve(0), @@ -1280,8 +1303,8 @@ { qRegisterMetaType(); - publishLimiter = new RateLimiter(this); - updateLimiter = new RateLimiter(this); + publishLimiter = std::make_unique(); + updateLimiter = std::make_unique(); httpSessionUpdateManager = new HttpSessionUpdateManager(this); @@ -1329,36 +1352,36 @@ log_info("zhttp in stream: %s", qPrintable(config.serverInStreamSpecs.join(", "))); log_info("zhttp out: %s", qPrintable(config.serverOutSpecs.join(", "))); - if(!config.inspectSpec.isEmpty()) + if(!config.inspectSpecs.isEmpty()) { inspectServer = new ZrpcManager(this); inspectServer->setBind(false); inspectServer->setIpcFileMode(config.ipcFileMode); inspectReqReadyConnection = inspectServer->requestReady.connect(boost::bind(&Private::inspectServer_requestReady, this)); - if(!inspectServer->setServerSpecs(QStringList() << config.inspectSpec)) + if(!inspectServer->setServerSpecs(config.inspectSpecs)) { // zrpcmanager logs error return false; } - log_info("inspect server: %s", qPrintable(config.inspectSpec)); + log_info("inspect server: %s", qPrintable(config.inspectSpecs.join(", "))); } - if(!config.acceptSpec.isEmpty()) + if(!config.acceptSpecs.isEmpty()) { acceptServer = new ZrpcManager(this); acceptServer->setBind(false); acceptServer->setIpcFileMode(config.ipcFileMode); acceptReqReadyConnection = acceptServer->requestReady.connect(boost::bind(&Private::acceptServer_requestReady, this)); - if(!acceptServer->setServerSpecs(QStringList() << config.acceptSpec)) + if(!acceptServer->setServerSpecs(config.acceptSpecs)) { // zrpcmanager logs error return false; } - log_info("accept server: %s", qPrintable(config.acceptSpec)); + log_info("accept server: %s", qPrintable(config.acceptSpecs.join(", "))); } if(!config.stateSpec.isEmpty()) @@ -1401,12 +1424,12 @@ QString errorMessage; if(!ZUtil::setupSocket(inPullSock, config.pushInSpec, true, config.ipcFileMode, &errorMessage)) { - log_error("%s", qPrintable(errorMessage)); - return false; + log_error("%s", qPrintable(errorMessage)); + return false; } inPullValve = new QZmq::Valve(inPullSock, this); - connect(inPullValve, &QZmq::Valve::readyRead, this, &Private::inPull_readyRead); + pullConnection = inPullValve->readyRead.connect(boost::bind(&Private::inPull_readyRead, this, boost::placeholders::_1)); log_info("in pull: %s", qPrintable(config.pushInSpec)); } @@ -1420,8 +1443,8 @@ QString errorMessage; if(!ZUtil::setupSocket(inSubSock, config.pushInSubSpecs, !config.pushInSubConnect, config.ipcFileMode, &errorMessage)) { - log_error("%s", qPrintable(errorMessage)); - return false; + log_error("%s", qPrintable(errorMessage)); + return false; } if(config.pushInSubConnect) @@ -1433,61 +1456,78 @@ } inSubValve = new QZmq::Valve(inSubSock, this); - connect(inSubValve, &QZmq::Valve::readyRead, this, &Private::inSub_readyRead); + inSubValveConnection = inSubValve->readyRead.connect(boost::bind(&Private::inSub_readyRead, this, boost::placeholders::_1)); log_info("in sub: %s", qPrintable(config.pushInSubSpecs.join(", "))); } - if(!config.retryOutSpec.isEmpty()) + if(!config.retryOutSpecs.isEmpty()) { - retrySock = new QZmq::Socket(QZmq::Socket::Push, this); + retrySock = new QZmq::Socket(QZmq::Socket::Router, this); + retrySock->setImmediateEnabled(true); retrySock->setHwm(DEFAULT_HWM); retrySock->setShutdownWaitTime(RETRY_WAIT_TIME); + retrySock->setRouterMandatoryEnabled(true); - QString errorMessage; - if(!ZUtil::setupSocket(retrySock, config.retryOutSpec, false, config.ipcFileMode, &errorMessage)) + foreach(const QString &spec, config.retryOutSpecs) { + QString errorMessage; + if(!ZUtil::setupSocket(retrySock, spec, false, config.ipcFileMode, &errorMessage)) + { log_error("%s", qPrintable(errorMessage)); return false; + } } - log_info("retry: %s", qPrintable(config.retryOutSpec)); + log_info("retry: %s", qPrintable(config.retryOutSpecs.join(", "))); } - if(!config.wsControlInSpec.isEmpty() && !config.wsControlOutSpec.isEmpty()) + if(!config.wsControlInitSpecs.isEmpty() && !config.wsControlStreamSpecs.isEmpty()) { - wsControlInSock = new QZmq::Socket(QZmq::Socket::Pull, this); - wsControlInSock->setHwm(DEFAULT_HWM); + wsControlInitSock = new QZmq::Socket(QZmq::Socket::Pull, this); + wsControlInitSock->setHwm(DEFAULT_HWM); - QString errorMessage; - if(!ZUtil::setupSocket(wsControlInSock, config.wsControlInSpec, false, config.ipcFileMode, &errorMessage)) + foreach(const QString &spec, config.wsControlInitSpecs) { + QString errorMessage; + if(!ZUtil::setupSocket(wsControlInitSock, spec, false, config.ipcFileMode, &errorMessage)) + { log_error("%s", qPrintable(errorMessage)); return false; + } } - wsControlInValve = new QZmq::Valve(wsControlInSock, this); - connect(wsControlInValve, &QZmq::Valve::readyRead, this, &Private::wsControlIn_readyRead); + wsControlInitValve = new QZmq::Valve(wsControlInitSock, this); + controlInitValveConnection = wsControlInitValve->readyRead.connect(boost::bind(&Private::wsControlInit_readyRead, this, boost::placeholders::_1)); - log_info("ws control in: %s", qPrintable(config.wsControlInSpec)); + log_info("ws control init: %s", qPrintable(config.wsControlInitSpecs.join(", "))); - wsControlOutSock = new QZmq::Socket(QZmq::Socket::Push, this); - wsControlOutSock->setHwm(DEFAULT_HWM); - wsControlOutSock->setShutdownWaitTime(WSCONTROL_WAIT_TIME); + wsControlStreamSock = new QZmq::Socket(QZmq::Socket::Router, this); + wsControlStreamSock->setIdentity(config.instanceId); + wsControlStreamSock->setImmediateEnabled(true); + wsControlStreamSock->setHwm(DEFAULT_HWM); + wsControlStreamSock->setShutdownWaitTime(WSCONTROL_WAIT_TIME); - if(!ZUtil::setupSocket(wsControlOutSock, config.wsControlOutSpec, false, config.ipcFileMode, &errorMessage)) + foreach(const QString &spec, config.wsControlStreamSpecs) { + QString errorMessage; + if(!ZUtil::setupSocket(wsControlStreamSock, spec, false, config.ipcFileMode, &errorMessage)) + { log_error("%s", qPrintable(errorMessage)); return false; + } } - log_info("ws control out: %s", qPrintable(config.wsControlOutSpec)); + wsControlStreamValve = new QZmq::Valve(wsControlStreamSock, this); + controlStreamValveConnection = wsControlStreamValve->readyRead.connect(boost::bind(&Private::wsControlStream_readyRead, this, boost::placeholders::_1)); + + log_info("ws control stream: %s", qPrintable(config.wsControlStreamSpecs.join(", "))); } stats = new StatsManager(config.connectionsMax, config.connectionsMax * config.connectionSubscriptionMax, this); - connect(stats, &StatsManager::connectionsRefreshed, this, &Private::stats_connectionsRefreshed); - connect(stats, &StatsManager::unsubscribed, this, &Private::stats_unsubscribed); - connect(stats, &StatsManager::reported, this, &Private::stats_reported); + connectionsRefreshedConnection = stats->connectionsRefreshed.connect(boost::bind(&Private::stats_connectionsRefreshed, this, boost::placeholders::_1)); + unsubscribedConnection = stats->unsubscribed.connect(boost::bind(&Private::stats_unsubscribed, this, boost::placeholders::_1, boost::placeholders::_2)); + reportedConnection = stats->reported.connect(boost::bind(&Private::stats_reported, this, boost::placeholders::_1)); stats->setConnectionSendEnabled(config.statsConnectionSend); stats->setConnectionTtl(config.statsConnectionTtl); @@ -1529,24 +1569,27 @@ } } - if(!config.proxyStatsSpec.isEmpty()) + if(!config.proxyStatsSpecs.isEmpty()) { proxyStatsSock = new QZmq::Socket(QZmq::Socket::Sub, this); proxyStatsSock->setHwm(DEFAULT_HWM); proxyStatsSock->setShutdownWaitTime(0); proxyStatsSock->subscribe(""); - QString errorMessage; - if(!ZUtil::setupSocket(proxyStatsSock, config.proxyStatsSpec, false, config.ipcFileMode, &errorMessage)) + foreach(const QString &spec, config.proxyStatsSpecs) { - log_error("%s", qPrintable(errorMessage)); - return false; + QString errorMessage; + if(!ZUtil::setupSocket(proxyStatsSock, spec, false, config.ipcFileMode, &errorMessage)) + { + log_error("%s", qPrintable(errorMessage)); + return false; + } } proxyStatsValve = new QZmq::Valve(proxyStatsSock, this); - connect(proxyStatsValve, &QZmq::Valve::readyRead, this, &Private::proxyStats_readyRead); + proxyStatConnection = proxyStatsValve->readyRead.connect(boost::bind(&Private::proxyStats_readyRead, this, boost::placeholders::_1)); - log_info("proxy stats: %s", qPrintable(config.proxyStatsSpec)); + log_info("proxy stats: %s", qPrintable(config.proxyStatsSpecs.join(", "))); } if(!config.proxyCommandSpec.isEmpty()) @@ -1577,8 +1620,10 @@ inPullValve->open(); if(inSubValve) inSubValve->open(); - if(wsControlInValve) - wsControlInValve->open(); + if(wsControlInitValve) + wsControlInitValve->open(); + if(wsControlStreamValve) + wsControlStreamValve->open(); if(proxyStatsValve) proxyStatsValve->open(); @@ -1602,7 +1647,7 @@ sequencer->addItem(item, seq); } - void writeRetryPacket(const RetryRequestPacket &packet) + void writeRetryPacket(const QByteArray &instanceAddress, const RetryRequestPacket &packet) { if(!retrySock) { @@ -1613,28 +1658,37 @@ QVariant vout = packet.toVariant(); if(log_outputLevel() >= LOG_LEVEL_DEBUG) - log_debug("OUT retry: %s", qPrintable(TnetString::variantToString(vout, -1))); + log_debug("OUT retry: to=%s %s", instanceAddress.data(), qPrintable(TnetString::variantToString(vout, -1))); - retrySock->write(QList() << TnetString::fromVariant(vout)); + QList msg; + msg += instanceAddress; + msg += QByteArray(); + msg += TnetString::fromVariant(vout); + retrySock->write(msg); } - void writeWsControlItems(const QList &items) + void writeWsControlItems(const QByteArray &instanceAddress, const QList &items) { - if(!wsControlOutSock) + if(!wsControlStreamSock) { log_error("wscontrol: can't write, no socket"); return; } WsControlPacket out; + out.from = config.instanceId; out.items = items; QVariant vout = out.toVariant(); if(log_outputLevel() >= LOG_LEVEL_DEBUG) - log_debug("OUT wscontrol: %s", qPrintable(TnetString::variantToString(vout, -1))); + log_debug("OUT wscontrol: to=%s %s", instanceAddress.data(), qPrintable(TnetString::variantToString(vout, -1))); - wsControlOutSock->write(QList() << TnetString::fromVariant(vout)); + QList msg; + msg += instanceAddress; + msg += QByteArray(); + msg += TnetString::fromVariant(vout); + wsControlStreamSock->write(msg); } void addSub(const QString &channel) @@ -1642,7 +1696,7 @@ if(!cs.subs.contains(channel)) { Subscription *sub = new Subscription(channel); - connect(sub, &Subscription::subscribed, this, &Private::sub_subscribed); + subscribedConnection[sub] = sub->subscribed.connect(boost::bind(&Private::sub_subscribed, this, sub)); cs.subs.insert(channel, sub); sub->start(); @@ -1660,6 +1714,7 @@ { Subscription *sub = cs.subs[channel]; cs.subs.remove(channel); + subscribedConnection.erase(sub); delete sub; sequencer->clearPendingForChannel(channel); @@ -1680,6 +1735,7 @@ log_debug("removed ws session: %s", qPrintable(s->cid)); cs.wsSessions.remove(s->cid); + wsSessionConnectionMap.erase(s); delete s; } @@ -1778,13 +1834,10 @@ } else if(f.action == PublishFormat::Refresh) { - Deferred *d = ControlRequest::refresh(proxyControlClient, i.cid, this); - finishedConnection[d] = d->finished.connect(boost::bind(&Private::deferred_finished, this, boost::placeholders::_1, d)); - deferreds += d; - return; + i.type = WsControlPacket::Item::Refresh; } - writeWsControlItems(QList() << i); + writeWsControlItems(s->peer, QList() << i); } } @@ -1944,10 +1997,10 @@ // accept request immediately before returning to the event loop. // the start() call will do this - AcceptWorker *w = new AcceptWorker(req, stateClient, &cs, zhttpIn, zhttpOut, stats, updateLimiter, httpSessionUpdateManager, config.connectionSubscriptionMax, this); + AcceptWorker *w = new AcceptWorker(req, stateClient, &cs, zhttpIn, zhttpOut, stats, updateLimiter.get(), httpSessionUpdateManager, config.connectionSubscriptionMax, this); finishedConnection[w] = w->finished.connect(boost::bind(&Private::acceptWorker_finished, this, boost::placeholders::_1, w)); - connect(w, &AcceptWorker::sessionsReady, this, &Private::acceptWorker_sessionsReady); - connect(w, &AcceptWorker::retryPacketReady, this, &Private::acceptWorker_retryPacketReady); + sessionsReadyConnection[w] = w->sessionsReady.connect(boost::bind(&Private::acceptWorker_sessionsReady, this, w)); + retryPacketReadyConnection[w] = w->retryPacketReady.connect(boost::bind(&Private::acceptWorker_retryPacketReady, this, boost::placeholders::_1, boost::placeholders::_2)); acceptWorkers += w; w->start(); @@ -1958,7 +2011,7 @@ if(args.contains("conn-max")) { - if(args["conn-max"].type() == QVariant::List) + if(typeId(args["conn-max"]) == QMetaType::QVariantList) { QVariantList packets = args["conn-max"].toList(); @@ -2032,7 +2085,7 @@ return; } - if(args["items"].type() != QVariant::List) + if(typeId(args["items"]) != QMetaType::QVariantList) { req->respondError("bad-request", "Invalid format: object contains 'items' with wrong type"); delete req; @@ -2319,6 +2372,8 @@ Q_UNUSED(result); finishedConnection.erase(w); + sessionsReadyConnection.erase(w); + retryPacketReadyConnection.erase(w); acceptWorkers.remove(w); // try to read again @@ -2333,7 +2388,99 @@ deferreds.remove(w); } -private slots: + void sub_subscribed(Subscription *sub) + { + updateSessions(sub->channel()); + } + + void acceptWorker_sessionsReady(AcceptWorker *w) + { + QList sessions = w->takeSessions(); + foreach(HttpSession *hs, sessions) + { + // NOTE: for performance reasons we do not call hs->setParent and + // instead leave the object unparented + + hs->subscribeCallback().add(Private::hs_subscribe_cb, this); + hs->unsubscribeCallback().add(Private::hs_unsubscribe_cb, this); + hs->finishedCallback().add(Private::hs_finished_cb, this); + + cs.httpSessions.insert(hs->rid(), hs); + + hs->start(); + } + } + + void acceptWorker_retryPacketReady(const QByteArray &instanceAddress, const RetryRequestPacket &packet) + { + writeRetryPacket(instanceAddress, packet); + } + + void stats_connectionsRefreshed(const QList &ids) + { + if(stateClient) + { + // find sids of the connections + QHash sidLastIds; + foreach(const QByteArray &id, ids) + { + int at = id.indexOf(':'); + assert(at != -1); + ZhttpRequest::Rid rid(id.mid(0, at), id.mid(at + 1)); + + HttpSession *hs = cs.httpSessions.value(rid); + if(hs && !hs->sid().isEmpty()) + sidLastIds[hs->sid()] = LastIds(); + } + + if(!sidLastIds.isEmpty()) + { + Deferred *d = SessionRequest::updateMany(stateClient, sidLastIds, this); + finishedConnection[d] = d->finished.connect(boost::bind(&Private::sessionUpdateMany_finished, this, boost::placeholders::_1, d)); + deferreds += d; + } + } + } + + void stats_unsubscribed(const QString &mode, const QString &channel) + { + // NOTE: this callback may be invoked while looping over certain structures, + // so be careful what you touch + + Q_UNUSED(mode); + + if(!cs.responseSessionsByChannel.contains(channel) && !cs.streamSessionsByChannel.contains(channel) && !cs.wsSessionsByChannel.contains(channel)) + removeSub(channel); + } + + void stats_reported(const QList &packets) + { + // only one outstanding report at a time + if(report) + return; + + // consolidate data + StatsPacket all; + all.type = StatsPacket::Report; + all.connectionsMax = 0; + all.connectionsMinutes = 0; + all.messagesReceived = 0; + all.messagesSent = 0; + all.httpResponseMessagesSent = 0; + foreach(const StatsPacket &p, packets) + { + all.connectionsMax += qMax(p.connectionsMax, 0); + all.connectionsMinutes += qMax(p.connectionsMinutes, 0); + all.messagesReceived += qMax(p.messagesReceived, 0); + all.messagesSent += qMax(p.messagesSent, 0); + all.httpResponseMessagesSent += qMax(p.httpResponseMessagesSent, 0); + } + + report = ControlRequest::report(proxyControlClient, all, this); + finishedConnection[report] = report->finished.connect(boost::bind(&Private::report_finished, this, boost::placeholders::_1)); + deferreds += report; + } + QVariant parseJsonOrTnetstring(const QByteArray &message, bool *ok = 0, QString *errorMessage = 0) { QVariant data; bool ok_; @@ -2445,7 +2592,7 @@ handlePublishItem(item); } - void wsControlIn_readyRead(const QList &message) + void wsControlInit_readyRead(const QList &message) { if(message.count() != 1) { @@ -2453,8 +2600,26 @@ return; } + wsControlIn_readyRead(message[0]); + } + + void wsControlStream_readyRead(const QList &message) + { + QZmq::ReqMessage req(message); + + if(req.content().count() != 1) + { + log_warning("IN wscontrol: received message with parts != 1, skipping"); + return; + } + + wsControlIn_readyRead(req.content()[0]); + } + + void wsControlIn_readyRead(const QByteArray &message) + { bool ok; - QVariant data = TnetString::toVariant(message[0], 0, &ok); + QVariant data = TnetString::toVariant(message, 0, &ok); if(!ok) { log_warning("IN wscontrol: received message with invalid format (tnetstring parse failed), skipping"); @@ -2494,9 +2659,12 @@ if(!s) { s = new WsSession(this); - connect(s, &WsSession::send, this, &Private::wssession_send); - connect(s, &WsSession::expired, this, &Private::wssession_expired); - connect(s, &WsSession::error, this, &Private::wssession_error); + wsSessionConnectionMap[s] = { + s->send.connect(boost::bind(&Private::wssession_send, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3, s)), + s->expired.connect(boost::bind(&Private::wssession_expired, this, s)), + s->error.connect(boost::bind(&Private::wssession_error, this, s)) + }; + s->peer = packet.from; s->cid = QString::fromUtf8(item.cid); s->ttl = item.ttl; s->requestData.uri = item.uri; @@ -2721,7 +2889,7 @@ } if(!outItems.isEmpty()) - writeWsControlItems(outItems); + writeWsControlItems(packet.from, outItems); if(stateClient) { @@ -2895,7 +3063,7 @@ return; } - if(mdata["items"].type() != QVariant::List) + if(typeId(mdata["items"]) != QMetaType::QVariantList) { httpControlRespond(req, 400, "Bad Request", "Invalid format: object contains 'items' with wrong type\n"); return; @@ -2967,31 +3135,7 @@ } } - void acceptWorker_sessionsReady() - { - AcceptWorker *w = (AcceptWorker *)sender(); - - QList sessions = w->takeSessions(); - foreach(HttpSession *hs, sessions) - { - // NOTE: for performance reasons we do not call hs->setParent and - // instead leave the object unparented - - hs->subscribeCallback().add(Private::hs_subscribe_cb, this); - hs->unsubscribeCallback().add(Private::hs_unsubscribe_cb, this); - hs->finishedCallback().add(Private::hs_finished_cb, this); - - cs.httpSessions.insert(hs->rid(), hs); - - hs->start(); - } - } - - void acceptWorker_retryPacketReady(const RetryRequestPacket &packet) - { - writeRetryPacket(packet); - } - +private slots: void hs_subscribe(HttpSession *hs, const QString &channel) { Instruct::HoldMode mode = hs->holdMode(); @@ -3037,6 +3181,7 @@ void hs_finished(HttpSession *hs) { + QByteArray addr = hs->retryToAddress(); RetryRequestPacket rp = hs->retryPacket(); cs.httpSessions.remove(hs->rid()); @@ -3047,13 +3192,11 @@ hs->deleteLater(); if(!rp.requests.isEmpty()) - writeRetryPacket(rp); + writeRetryPacket(addr, rp); } - void wssession_send(int reqId, const QByteArray &type, const QByteArray &message) + void wssession_send(int reqId, const QByteArray &type, const QByteArray &message, WsSession *s) { - WsSession *s = (WsSession *)sender(); - WsControlPacket::Item i; i.cid = s->cid.toUtf8(); i.requestId = QByteArray::number(reqId); @@ -3062,102 +3205,26 @@ i.message = message; i.queue = true; - writeWsControlItems(QList() << i); + writeWsControlItems(s->peer, QList() << i); } - void wssession_expired() + void wssession_expired(WsSession *s) { - WsSession *s = (WsSession *)sender(); - removeWsSession(s); } - void wssession_error() + void wssession_error(WsSession *s) { - WsSession *s = (WsSession *)sender(); - log_debug("ws session %s control error", qPrintable(s->cid)); WsControlPacket::Item i; i.cid = s->cid.toUtf8(); i.type = WsControlPacket::Item::Cancel; - writeWsControlItems(QList() << i); + writeWsControlItems(s->peer, QList() << i); removeWsSession(s); } - - void sub_subscribed() - { - Subscription *sub = (Subscription *)sender(); - - updateSessions(sub->channel()); - } - - void stats_connectionsRefreshed(const QList &ids) - { - if(stateClient) - { - // find sids of the connections - QHash sidLastIds; - foreach(const QByteArray &id, ids) - { - int at = id.indexOf(':'); - assert(at != -1); - ZhttpRequest::Rid rid(id.mid(0, at), id.mid(at + 1)); - - HttpSession *hs = cs.httpSessions.value(rid); - if(hs && !hs->sid().isEmpty()) - sidLastIds[hs->sid()] = LastIds(); - } - - if(!sidLastIds.isEmpty()) - { - Deferred *d = SessionRequest::updateMany(stateClient, sidLastIds, this); - finishedConnection[d] = d->finished.connect(boost::bind(&Private::sessionUpdateMany_finished, this, boost::placeholders::_1, d)); - deferreds += d; - } - } - } - - void stats_unsubscribed(const QString &mode, const QString &channel) - { - // NOTE: this callback may be invoked while looping over certain structures, - // so be careful what you touch - - Q_UNUSED(mode); - - if(!cs.responseSessionsByChannel.contains(channel) && !cs.streamSessionsByChannel.contains(channel) && !cs.wsSessionsByChannel.contains(channel)) - removeSub(channel); - } - - void stats_reported(const QList &packets) - { - // only one outstanding report at a time - if(report) - return; - - // consolidate data - StatsPacket all; - all.type = StatsPacket::Report; - all.connectionsMax = 0; - all.connectionsMinutes = 0; - all.messagesReceived = 0; - all.messagesSent = 0; - all.httpResponseMessagesSent = 0; - foreach(const StatsPacket &p, packets) - { - all.connectionsMax += qMax(p.connectionsMax, 0); - all.connectionsMinutes += qMax(p.connectionsMinutes, 0); - all.messagesReceived += qMax(p.messagesReceived, 0); - all.messagesSent += qMax(p.messagesSent, 0); - all.httpResponseMessagesSent += qMax(p.httpResponseMessagesSent, 0); - } - - report = ControlRequest::report(proxyControlClient, all, this); - finishedConnection[report] = report->finished.connect(boost::bind(&Private::report_finished, this, boost::placeholders::_1)); - deferreds += report; - } }; HandlerEngine::HandlerEngine(QObject *parent) : diff -Nru pushpin-1.38.0/src/cpp/handler/handlerengine.h pushpin-1.39.1/src/cpp/handler/handlerengine.h --- pushpin-1.38.0/src/cpp/handler/handlerengine.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/handlerengine.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2015-2023 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -30,6 +31,7 @@ #include using std::map; +using Signal = boost::signals2::signal; using Connection = boost::signals2::scoped_connection; class HandlerEngine : public QObject @@ -47,15 +49,15 @@ QStringList clientOutSpecs; QStringList clientOutStreamSpecs; QStringList clientInSpecs; - QString inspectSpec; - QString acceptSpec; - QString retryOutSpec; - QString wsControlInSpec; - QString wsControlOutSpec; + QStringList inspectSpecs; + QStringList acceptSpecs; + QStringList retryOutSpecs; + QStringList wsControlInitSpecs; + QStringList wsControlStreamSpecs; QString statsSpec; QString commandSpec; QString stateSpec; - QString proxyStatsSpec; + QStringList proxyStatsSpecs; QString proxyCommandSpec; QString pushInSpec; QStringList pushInSubSpecs; diff -Nru pushpin-1.38.0/src/cpp/handler/handlermain.cpp pushpin-1.39.1/src/cpp/handler/handlermain.cpp --- pushpin-1.38.0/src/cpp/handler/handlermain.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/handlermain.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,6 +23,7 @@ #include #include +#include "rtimer.h" #include "handlerapp.h" class HandlerAppMain @@ -38,7 +40,7 @@ void app_quit(int returnCode) { - delete app; + delete app; QCoreApplication::exit(returnCode); } }; @@ -51,7 +53,15 @@ HandlerAppMain appMain; QTimer::singleShot(0, [&appMain]() {appMain.start();}); - return qapp.exec(); + int ret = qapp.exec(); + + // ensure deferred deletes are processed + QCoreApplication::instance()->sendPostedEvents(); + + // deinit here, after all event loop activity has completed + RTimer::deinit(); + + return ret; } } diff -Nru pushpin-1.38.0/src/cpp/handler/httpsession.cpp pushpin-1.39.1/src/cpp/handler/httpsession.cpp --- pushpin-1.38.0/src/cpp/handler/httpsession.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/httpsession.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2016-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -29,6 +29,7 @@ #include #include #include +#include "qtcompat.h" #include "rtimer.h" #include "log.h" #include "bufferlist.h" @@ -72,10 +73,10 @@ vbody = JsonPatch::patch(vbody, bodyPatch, &errorMessage); if(vbody.isValid()) vbody = VariantUtil::convertToJsonStyle(vbody); - if(vbody.isValid() && (vbody.type() == QVariant::Map || vbody.type() == QVariant::List)) + if(vbody.isValid() && (typeId(vbody) == QMetaType::QVariantMap || typeId(vbody) == QMetaType::QVariantList)) { QJsonDocument doc; - if(vbody.type() == QVariant::Map) + if(typeId(vbody) == QMetaType::QVariantMap) doc = QJsonDocument(QJsonObject::fromVariantMap(vbody.toMap())); else // List doc = QJsonDocument(QJsonArray::fromVariantList(vbody.toList())); @@ -167,6 +168,7 @@ Priority needUpdatePriority; UpdateAction *pendingAction; QList publishQueue; + QByteArray retryToAddress; RetryRequestPacket retryPacket; LogUtil::Config logConfig; FilterStack *responseFilters; @@ -183,7 +185,7 @@ Connection readyReadOutConnection; Connection errorOutConnection; Connection timerConnection; - Connection retryTimerConneciton; + Connection retryTimerConnection; Private(HttpSession *_q, ZhttpRequest *_req, const HttpSession::AcceptData &_adata, const Instruct &_instruct, ZhttpManager *_outZhttp, StatsManager *_stats, RateLimiter *_updateLimiter, PublishLastIds *_publishLastIds, HttpSessionUpdateManager *_updateManager, int _connectionSubscriptionMax) : QObject(_q), @@ -211,11 +213,11 @@ writeBytesChangedConnection = req->writeBytesChanged.connect(boost::bind(&Private::req_writeBytesChanged, this)); errorConnection = req->error.connect(boost::bind(&Private::req_error, this)); - timer = new RTimer(this); + timer = new RTimer; timerConnection = timer->timeout.connect(boost::bind(&Private::timer_timeout, this)); - retryTimer = new RTimer(this); - retryTimerConneciton = retryTimer->timeout.connect(boost::bind(&Private::retryTimer_timeout, this)); + retryTimer = new RTimer; + retryTimerConnection = retryTimer->timeout.connect(boost::bind(&Private::retryTimer_timeout, this)); retryTimer->setSingleShot(true); adata = _adata; @@ -245,7 +247,7 @@ timer->setParent(0); timer->deleteLater(); - retryTimerConneciton.disconnect(); + retryTimerConnection.disconnect(); retryTimer->setParent(0); retryTimer->deleteLater(); } @@ -1080,7 +1082,7 @@ needRemoveFromStats = false; - int unreportedTime = stats->removeConnection(cid, true); + int unreportedTime = stats->removeConnection(cid, true, adata.from); ZhttpRequest::ServerState ss = req->serverState(); @@ -1136,8 +1138,9 @@ } rp.route = adata.route.toUtf8(); - rp.retrySeq = stats->lastRetrySeq(); + rp.retrySeq = stats->lastRetrySeq(adata.from); + retryToAddress = adata.from; retryPacket = rp; } else @@ -1350,7 +1353,8 @@ { LogUtil::RequestData rd; - if(!adata.route.isEmpty()) + // only log route id if explicitly set + if(!adata.statsRoute.isEmpty()) rd.routeId = adata.route; rd.status = LogUtil::Response; @@ -1369,7 +1373,8 @@ { LogUtil::RequestData rd; - if(!adata.route.isEmpty()) + // only log route id if explicitly set + if(!adata.statsRoute.isEmpty()) rd.routeId = adata.route; rd.status = LogUtil::Error; @@ -1599,6 +1604,11 @@ return d->instruct.meta; } +QByteArray HttpSession::retryToAddress() const +{ + return d->retryToAddress; +} + RetryRequestPacket HttpSession::retryPacket() const { return d->retryPacket; diff -Nru pushpin-1.38.0/src/cpp/handler/httpsession.h pushpin-1.39.1/src/cpp/handler/httpsession.h --- pushpin-1.38.0/src/cpp/handler/httpsession.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/httpsession.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-2023 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -53,6 +54,7 @@ class AcceptData { public: + QByteArray from; QHostAddress logicalPeerAddress; bool debug; bool isRetry; @@ -95,6 +97,7 @@ QString sid() const; QHash channels() const; QHash meta() const; + QByteArray retryToAddress() const; RetryRequestPacket retryPacket() const; void start(); diff -Nru pushpin-1.38.0/src/cpp/handler/httpsessionupdatemanager.cpp pushpin-1.39.1/src/cpp/handler/httpsessionupdatemanager.cpp --- pushpin-1.38.0/src/cpp/handler/httpsessionupdatemanager.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/httpsessionupdatemanager.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -113,7 +113,9 @@ bucket->key = key; bucket->sessions += hs; bucket->timer = new QTimer(this); - connect(bucket->timer, &QTimer::timeout, this, &Private::timer_timeout); + QObject::connect(bucket->timer, &QTimer::timeout, [this, timer=bucket->timer]() { + this->timer_timeout(timer); + }); buckets[key] = bucket; bucketsByTimer[bucket->timer] = bucket; @@ -137,10 +139,9 @@ removeBucket(bucket); } -private slots: - void timer_timeout() +private: + void timer_timeout(QTimer *timer) { - QTimer *timer = (QTimer *)sender(); Bucket *bucket = bucketsByTimer.value(timer); if(!bucket) return; diff -Nru pushpin-1.38.0/src/cpp/handler/instruct.cpp pushpin-1.39.1/src/cpp/handler/instruct.cpp --- pushpin-1.38.0/src/cpp/handler/instruct.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/instruct.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-2019 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -25,6 +26,7 @@ #include #include #include +#include "qtcompat.h" #include "variantutil.h" #include "statusreasons.h" @@ -381,7 +383,7 @@ if(minstruct.contains("hold")) { - if(minstruct["hold"].type() != QVariant::Map) + if(typeId(minstruct["hold"]) != QMetaType::QVariantMap) { setError(ok, errorMessage, "instruct contains 'hold' with wrong type"); return Instruct(); @@ -475,7 +477,7 @@ if(keyedObjectContains(vhold, "timeout")) { QVariant vtimeout = keyedObjectGetValue(vhold, "timeout"); - if(!vtimeout.canConvert(QVariant::Int)) + if(!canConvert(vtimeout, QMetaType::Int)) { setError(ok, errorMessage, QString("%1 contains 'timeout' with wrong type").arg(pn)); return Instruct(); @@ -517,9 +519,9 @@ else if(keyedObjectContains(vka, "content")) { QVariant vcontent = keyedObjectGetValue(vka, "content"); - if(vcontent.type() == QVariant::ByteArray) + if(typeId(vcontent) == QMetaType::QByteArray) keepAliveData = vcontent.toByteArray(); - else if(vcontent.type() == QVariant::String) + else if(typeId(vcontent) == QMetaType::QString) keepAliveData = vcontent.toString().toUtf8(); else { @@ -531,7 +533,7 @@ if(keyedObjectContains(vka, "timeout")) { QVariant vtimeout = keyedObjectGetValue(vka, "timeout"); - if(!vtimeout.canConvert(QVariant::Int)) + if(!canConvert(vtimeout, QMetaType::Int)) { setError(ok, errorMessage, QString("%1 contains 'timeout' with wrong type").arg(kpn)); return Instruct(); @@ -561,7 +563,7 @@ if(vmeta.isValid()) { - if(vmeta.type() == QVariant::Hash) + if(typeId(vmeta) == QMetaType::QVariantHash) { QVariantHash hmeta = vmeta.toHash(); @@ -611,7 +613,7 @@ if(minstruct.contains("response")) { - if(minstruct["response"].type() != QVariant::Map) + if(typeId(minstruct["response"]) != QMetaType::QVariantMap) { if(ok) *ok = false; @@ -625,7 +627,7 @@ if(keyedObjectContains(in, "code")) { QVariant vcode = keyedObjectGetValue(in, "code"); - if(!vcode.canConvert(QVariant::Int)) + if(!canConvert(vcode, QMetaType::Int)) { setError(ok, errorMessage, QString("%1 contains 'code' with wrong type").arg(pn)); return Instruct(); @@ -658,11 +660,11 @@ if(keyedObjectContains(in, "headers")) { QVariant vheaders = keyedObjectGetValue(in, "headers"); - if(vheaders.type() == QVariant::List) + if(typeId(vheaders) == QMetaType::QVariantList) { foreach(const QVariant &vheader, vheaders.toList()) { - if(vheader.type() != QVariant::List) + if(typeId(vheader) != QMetaType::QVariantList) { setError(ok, errorMessage, "headers contains element with wrong type"); return Instruct(); @@ -694,7 +696,7 @@ } else if(isKeyedObject(vheaders)) { - if(vheaders.type() == QVariant::Hash) + if(typeId(vheaders) == QMetaType::QVariantHash) { QVariantHash hheaders = vheaders.toHash(); @@ -759,9 +761,9 @@ else if(keyedObjectContains(in, "body")) { QVariant vcontent = keyedObjectGetValue(in, "body"); - if(vcontent.type() == QVariant::ByteArray) + if(typeId(vcontent) == QMetaType::QByteArray) newResponse.body = vcontent.toByteArray(); - else if(vcontent.type() == QVariant::String) + else if(typeId(vcontent) == QMetaType::QString) newResponse.body = vcontent.toString().toUtf8(); else { diff -Nru pushpin-1.38.0/src/cpp/handler/jsonpatch.cpp pushpin-1.39.1/src/cpp/handler/jsonpatch.cpp --- pushpin-1.38.0/src/cpp/handler/jsonpatch.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/jsonpatch.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,6 +24,7 @@ #include "jsonpatch.h" #include +#include "qtcompat.h" #include "jsonpointer.h" namespace JsonPatch { @@ -45,14 +47,14 @@ static bool isKeyedObject(const QVariant &in) { - return (in.type() == QVariant::Hash || in.type() == QVariant::Map); + return (typeId(in) == QMetaType::QVariantHash || typeId(in) == QMetaType::QVariantMap); } static bool keyedObjectContains(const QVariant &in, const QString &name) { - if(in.type() == QVariant::Hash) + if(typeId(in) == QMetaType::QVariantHash) return in.toHash().contains(name); - else if(in.type() == QVariant::Map) + else if(typeId(in) == QMetaType::QVariantMap) return in.toMap().contains(name); else return false; @@ -60,9 +62,9 @@ static QVariant keyedObjectGetValue(const QVariant &in, const QString &name) { - if(in.type() == QVariant::Hash) + if(typeId(in) == QMetaType::QVariantHash) return in.toHash().value(name); - else if(in.type() == QVariant::Map) + else if(typeId(in) == QMetaType::QVariantMap) return in.toMap().value(name); else return QVariant(); @@ -80,7 +82,7 @@ QString pn = !parentName.isEmpty() ? parentName : QString("object"); QVariant v; - if(in.type() == QVariant::Hash) + if(typeId(in) == QMetaType::QVariantHash) { QVariantHash h = in.toHash(); @@ -119,13 +121,13 @@ static QString getString(const QVariant &in, bool *ok = 0) { - if(in.type() == QVariant::String) + if(typeId(in) == QMetaType::QString) { if(ok) *ok = true; return in.toString(); } - else if(in.type() == QVariant::ByteArray) + else if(typeId(in) == QMetaType::QByteArray) { if(ok) *ok = true; @@ -177,8 +179,8 @@ bool changed = false; - int type = in->type(); - if(type == QVariant::Hash) + QMetaType::Type type = typeId(*in); + if(type == QMetaType::QVariantHash) { QVariantMap vmap; QVariantHash vhash = in->toHash(); @@ -194,7 +196,7 @@ *in = vmap; changed = true; } - else if(type == QVariant::List) + else if(type == QMetaType::QVariantList) { QVariantList vlist = in->toList(); for(int n = 0; n < vlist.count(); ++n) @@ -207,7 +209,7 @@ *in = vlist; changed = true; } - else if(type == QVariant::ByteArray) + else if(type == QMetaType::QByteArray) { *in = QVariant(QString::fromUtf8(in->toByteArray())); changed = true; @@ -225,7 +227,7 @@ static bool _compareJsonValues(const QVariant &a, const QVariant &b) { - if(a.type() == QVariant::Map && b.type() == QVariant::Map) + if(typeId(a) == QMetaType::QVariantMap && typeId(b) == QMetaType::QVariantMap) { QVariantMap am = a.toMap(); QVariantMap bm = b.toMap(); @@ -248,7 +250,7 @@ return true; } - else if(a.type() == QVariant::List && b.type() == QVariant::List) + else if(typeId(a) == QMetaType::QVariantList && typeId(b) == QMetaType::QVariantList) { QVariantList al = a.toList(); QVariantList bl = b.toList(); @@ -264,19 +266,19 @@ return true; } - else if(a.type() == QVariant::String && b.type() == QVariant::String) + else if(typeId(a) == QMetaType::QString && typeId(b) == QMetaType::QString) { return (a.toString() == b.toString()); } - else if(a.type() == QVariant::Bool && b.type() == QVariant::Bool) + else if(typeId(a) == QMetaType::Bool && typeId(b) == QMetaType::Bool) { return (a.toBool() == b.toBool()); } - else if(a.type() == QVariant::Invalid && b.type() == QVariant::Invalid) + else if(typeId(a) == QMetaType::UnknownType && typeId(b) == QMetaType::UnknownType) { return true; } - else if(a.canConvert(QVariant::Int) && b.canConvert(QVariant::Int)) + else if(canConvert(a, QMetaType::Int) && canConvert(b, QMetaType::Int)) { return (a.toInt() == b.toInt()); } diff -Nru pushpin-1.38.0/src/cpp/handler/jsonpointer.cpp pushpin-1.39.1/src/cpp/handler/jsonpointer.cpp --- pushpin-1.38.0/src/cpp/handler/jsonpointer.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/jsonpointer.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -24,6 +25,7 @@ #include #include +#include "qtcompat.h" JsonPointer::JsonPointer() : isNull_(true) @@ -46,7 +48,7 @@ if(ref.type == Ref::Object) { - if(i->type() == QVariant::Hash) + if(typeId(*i) == QMetaType::QVariantHash) { QVariantHash h = i->toHash(); if(!h.contains(ref.name)) @@ -75,9 +77,9 @@ // ensure ref is correct type const Ref &ref = refs_[refIndex]; - if(ref.type == Ref::Object && (i->type() != QVariant::Hash && i->type() != QVariant::Map)) + if(ref.type == Ref::Object && (typeId(*i) != QMetaType::QVariantHash && typeId(*i) != QMetaType::QVariantMap)) return ExecError; - else if(ref.type == Ref::Array && i->type() != QVariant::List) + else if(ref.type == Ref::Array && typeId(*i) != QMetaType::QVariantList) return ExecError; func(i, refs_[refIndex], data); @@ -92,7 +94,7 @@ const Ref &ref = refs_[refIndex]; if(ref.type == Ref::Object) { - if(i->type() == QVariant::Hash) + if(typeId(*i) == QMetaType::QVariantHash) { QVariantHash h = i->toHash(); if(!h.contains(ref.name)) @@ -130,9 +132,9 @@ // ensure ref is correct type const Ref &ref = refs_[refIndex]; - if(ref.type == Ref::Object && (i->type() != QVariant::Hash && i->type() != QVariant::Map)) + if(ref.type == Ref::Object && (typeId(*i) != QMetaType::QVariantHash && typeId(*i) != QMetaType::QVariantMap)) return ExecError; - else if(ref.type == Ref::Array && i->type() != QVariant::List) + else if(ref.type == Ref::Array && typeId(*i) != QMetaType::QVariantList) return ExecError; if(func(i, refs_[refIndex], data)) @@ -177,7 +179,7 @@ } else if(ref.type == JsonPointer::Ref::Object) { - if(v->type() == QVariant::Hash) + if(typeId(*v) == QMetaType::QVariantHash) ret = v->toHash().contains(ref.name); else // Map ret = v->toMap().contains(ref.name); @@ -208,7 +210,7 @@ } else if(ref.type == JsonPointer::Ref::Object) { - if(v->type() == QVariant::Hash) + if(typeId(*v) == QMetaType::QVariantHash) ret = v->toHash().value(ref.name); else // Map ret = v->toMap().value(ref.name); @@ -241,7 +243,7 @@ } else if(ref.type == JsonPointer::Ref::Object) { - if(v->type() == QVariant::Hash) + if(typeId(*v) == QMetaType::QVariantHash) { QVariantHash h = v->toHash(); if(h.contains(ref.name)) @@ -303,7 +305,7 @@ } else if(ref.type == JsonPointer::Ref::Object) { - if(v->type() == QVariant::Hash) + if(typeId(*v) == QMetaType::QVariantHash) { QVariantHash h = v->toHash(); if(h.contains(ref.name)) @@ -366,7 +368,7 @@ } else if(ref.type == JsonPointer::Ref::Object) { - if(v->type() == QVariant::Hash) + if(typeId(*v) == QMetaType::QVariantHash) { QVariantHash h = v->toHash(); h[ref.name] = data.first; @@ -475,9 +477,9 @@ if(prevRef.type == Ref::Object) { - assert(i.type() == QVariant::Hash || i.type() == QVariant::Map); + assert(typeId(i) == QMetaType::QVariantHash || typeId(i) == QMetaType::QVariantMap); - if(i.type() == QVariant::Hash) + if(typeId(i) == QMetaType::QVariantHash) { QVariantHash h = i.toHash(); if(!h.contains(prevRef.name)) @@ -516,11 +518,11 @@ } } - if(i.type() == QVariant::Hash || i.type() == QVariant::Map) + if(typeId(i) == QMetaType::QVariantHash || typeId(i) == QMetaType::QVariantMap) { ptr.refs_ += Ref(p); } - else if(i.type() == QVariant::List) + else if(typeId(i) == QMetaType::QVariantList) { QVariantList l = i.toList(); if(p == "-") diff -Nru pushpin-1.38.0/src/cpp/handler/publishformat.cpp pushpin-1.39.1/src/cpp/handler/publishformat.cpp --- pushpin-1.38.0/src/cpp/handler/publishformat.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/publishformat.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-2020 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,6 +23,7 @@ #include "publishformat.h" +#include "qtcompat.h" #include "variantutil.h" #include "statusreasons.h" @@ -84,7 +86,7 @@ if(keyedObjectContains(in, "code")) { QVariant vcode = keyedObjectGetValue(in, "code"); - if(!vcode.canConvert(QVariant::Int)) + if(!canConvert(vcode, QMetaType::Int)) { setError(ok, errorMessage, QString("%1 contains 'code' with wrong type").arg(pn)); return PublishFormat(); @@ -117,11 +119,11 @@ if(keyedObjectContains(in, "headers")) { QVariant vheaders = keyedObjectGetValue(in, "headers"); - if(vheaders.type() == QVariant::List) + if(typeId(vheaders) == QMetaType::QVariantList) { foreach(const QVariant &vheader, vheaders.toList()) { - if(vheader.type() != QVariant::List) + if(typeId(vheader) != QMetaType::QVariantList) { setError(ok, errorMessage, "headers contains element with wrong type"); return PublishFormat(); @@ -153,7 +155,7 @@ } else if(isKeyedObject(vheaders)) { - if(vheaders.type() == QVariant::Hash) + if(typeId(vheaders) == QMetaType::QVariantHash) { QVariantHash hheaders = vheaders.toHash(); @@ -206,7 +208,7 @@ if(keyedObjectContains(in, "content-filters")) { QVariant vfilters = keyedObjectGetValue(in, "content-filters"); - if(vfilters.type() != QVariant::List) + if(typeId(vfilters) != QMetaType::QVariantList) { setError(ok, errorMessage, QString("%1 contains 'content-filters' with wrong type").arg(pn)); return PublishFormat(); @@ -229,7 +231,7 @@ out.contentFilters = filters; } - if(in.type() == QVariant::Map && keyedObjectContains(in, "body-bin")) // JSON input + if(typeId(in) == QMetaType::QVariantMap && keyedObjectContains(in, "body-bin")) // JSON input { QString bodyBin = getString(in, pn, "body-bin", false, &ok_, errorMessage); if(!ok_) @@ -244,9 +246,9 @@ else if(keyedObjectContains(in, "body")) { QVariant vcontent = keyedObjectGetValue(in, "body"); - if(vcontent.type() == QVariant::ByteArray) + if(typeId(vcontent) == QMetaType::QByteArray) out.body = vcontent.toByteArray(); - else if(vcontent.type() == QVariant::String) + else if(typeId(vcontent) == QMetaType::QString) out.body = vcontent.toString().toUtf8(); else { @@ -268,7 +270,7 @@ } else { - if(in.type() == QVariant::Map) // JSON input + if(typeId(in) == QMetaType::QVariantMap) // JSON input setError(ok, errorMessage, QString("%1 does not contain 'body', 'body-bin', or 'body-patch'").arg(pn)); else setError(ok, errorMessage, QString("%1 does not contain 'body' or 'body-patch'").arg(pn)); @@ -283,7 +285,7 @@ if(keyedObjectContains(in, "content-filters")) { QVariant vfilters = keyedObjectGetValue(in, "content-filters"); - if(vfilters.type() != QVariant::List) + if(typeId(vfilters) != QMetaType::QVariantList) { setError(ok, errorMessage, QString("%1 contains 'content-filters' with wrong type").arg(pn)); return PublishFormat(); @@ -306,7 +308,7 @@ out.contentFilters = filters; } - if(in.type() == QVariant::Map && keyedObjectContains(in, "content-bin")) // JSON input + if(typeId(in) == QMetaType::QVariantMap && keyedObjectContains(in, "content-bin")) // JSON input { QString contentBin = getString(in, pn, "content-bin", false, &ok_, errorMessage); if(!ok_) @@ -321,9 +323,9 @@ else if(keyedObjectContains(in, "content")) { QVariant vcontent = keyedObjectGetValue(in, "content"); - if(vcontent.type() == QVariant::ByteArray) + if(typeId(vcontent) == QMetaType::QByteArray) out.body = vcontent.toByteArray(); - else if(vcontent.type() == QVariant::String) + else if(typeId(vcontent) == QMetaType::QString) out.body = vcontent.toString().toUtf8(); else { @@ -333,7 +335,7 @@ } else { - if(in.type() == QVariant::Map) // JSON input + if(typeId(in) == QMetaType::QVariantMap) // JSON input setError(ok, errorMessage, QString("%1 does not contain 'content' or 'content-bin'").arg(pn)); else setError(ok, errorMessage, QString("%1 does not contain 'content'").arg(pn)); @@ -373,7 +375,7 @@ if(keyedObjectContains(in, "content-filters")) { QVariant vfilters = keyedObjectGetValue(in, "content-filters"); - if(vfilters.type() != QVariant::List) + if(typeId(vfilters) != QMetaType::QVariantList) { setError(ok, errorMessage, QString("%1 contains 'content-filters' with wrong type").arg(pn)); return PublishFormat(); @@ -400,9 +402,9 @@ { QVariant vcontentBin = keyedObjectGetValue(in, "content-bin"); - if(in.type() == QVariant::Map) // JSON input + if(typeId(in) == QMetaType::QVariantMap) // JSON input { - if(vcontentBin.type() != QVariant::String) + if(typeId(vcontentBin) != QMetaType::QString) { setError(ok, errorMessage, QString("%1 contains 'content-bin' with wrong type").arg(pn)); return PublishFormat(); @@ -412,7 +414,7 @@ } else { - if(vcontentBin.type() != QVariant::ByteArray) + if(typeId(vcontentBin) != QMetaType::QByteArray) { setError(ok, errorMessage, QString("%1 contains 'content-bin' with wrong type").arg(pn)); return PublishFormat(); @@ -427,9 +429,9 @@ else if(keyedObjectContains(in, "content")) { QVariant vcontent = keyedObjectGetValue(in, "content"); - if(vcontent.type() == QVariant::ByteArray) + if(typeId(vcontent) == QMetaType::QByteArray) out.body = vcontent.toByteArray(); - else if(vcontent.type() == QVariant::String) + else if(typeId(vcontent) == QMetaType::QString) out.body = vcontent.toString().toUtf8(); else { @@ -451,7 +453,7 @@ if(keyedObjectContains(in, "code")) { QVariant vcode = keyedObjectGetValue(in, "code"); - if(!vcode.canConvert(QVariant::Int)) + if(!canConvert(vcode, QMetaType::Int)) { setError(ok, errorMessage, QString("%1 contains 'code' with wrong type").arg(pn)); return PublishFormat(); diff -Nru pushpin-1.38.0/src/cpp/handler/publishitem.cpp pushpin-1.39.1/src/cpp/handler/publishitem.cpp --- pushpin-1.38.0/src/cpp/handler/publishitem.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/publishitem.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,6 +23,7 @@ #include "publishitem.h" +#include "qtcompat.h" #include "variantutil.h" using namespace VariantUtil; @@ -150,7 +152,7 @@ if(vmeta.isValid()) { - if(vmeta.type() == QVariant::Hash) + if(typeId(vmeta) == QMetaType::QVariantHash) { QVariantHash hmeta = vmeta.toHash(); @@ -197,7 +199,7 @@ if(keyedObjectContains(vitem, "size")) { QVariant vsize = keyedObjectGetValue(vitem, "size"); - if(!vsize.canConvert(QVariant::Int)) + if(!canConvert(vsize, QMetaType::Int)) { setError(ok, errorMessage, QString("%1 contains 'size' with wrong type").arg(pn)); return PublishItem(); @@ -215,7 +217,7 @@ if(keyedObjectContains(vitem, "no-seq")) { QVariant vnoSeq = keyedObjectGetValue(vitem, "no-seq"); - if(vnoSeq.type() != QVariant::Bool) + if(typeId(vnoSeq) != QMetaType::Bool) { setError(ok, errorMessage, QString("%1 contains 'no-seq' with wrong type").arg(pn)); return PublishItem(); diff -Nru pushpin-1.38.0/src/cpp/handler/ratelimiter.cpp pushpin-1.39.1/src/cpp/handler/ratelimiter.cpp --- pushpin-1.38.0/src/cpp/handler/ratelimiter.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/ratelimiter.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -299,16 +299,12 @@ } }; -RateLimiter::RateLimiter(QObject *parent) : - QObject(parent) +RateLimiter::RateLimiter() { - d = new Private(this); + d = std::make_unique(this); } -RateLimiter::~RateLimiter() -{ - delete d; -} +RateLimiter::~RateLimiter() = default; void RateLimiter::setRate(int actionsPerSecond) { diff -Nru pushpin-1.38.0/src/cpp/handler/ratelimiter.h pushpin-1.39.1/src/cpp/handler/ratelimiter.h --- pushpin-1.38.0/src/cpp/handler/ratelimiter.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/ratelimiter.h 2024-03-18 18:42:24.000000000 +0000 @@ -23,6 +23,7 @@ #ifndef RATELIMITER_H #define RATELIMITER_H +#include #include class RateLimiter : public QObject @@ -38,7 +39,7 @@ virtual bool execute() = 0; }; - RateLimiter(QObject *parent = 0); + RateLimiter(); ~RateLimiter(); void setRate(int actionsPerSecond); @@ -50,7 +51,7 @@ private: class Private; - Private *d; + std::unique_ptr d; }; #endif diff -Nru pushpin-1.38.0/src/cpp/handler/refreshworker.cpp pushpin-1.39.1/src/cpp/handler/refreshworker.cpp --- pushpin-1.38.0/src/cpp/handler/refreshworker.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/refreshworker.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2017-2020 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,6 +23,7 @@ #include "refreshworker.h" +#include "qtcompat.h" #include "zrpcrequest.h" #include "controlrequest.h" #include "statsmanager.h" @@ -39,7 +41,7 @@ if(args.contains("cid")) { - if(args["cid"].type() != QVariant::ByteArray) + if(typeId(args["cid"]) != QMetaType::QByteArray) { respondError("bad-request"); return; @@ -51,7 +53,7 @@ } else if(args.contains("channel")) { - if(args["channel"].type() != QVariant::ByteArray) + if(typeId(args["channel"]) != QMetaType::QByteArray) { respondError("bad-request"); return; diff -Nru pushpin-1.38.0/src/cpp/handler/requeststate.cpp pushpin-1.39.1/src/cpp/handler/requeststate.cpp --- pushpin-1.38.0/src/cpp/handler/requeststate.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/requeststate.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-2023 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,45 +23,47 @@ #include "requeststate.h" +#include "qtcompat.h" + RequestState RequestState::fromVariant(const QVariant &in) { - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) return RequestState(); QVariantHash r = in.toHash(); RequestState rs; - if(!r.contains("rid") || r["rid"].type() != QVariant::Hash) + if(!r.contains("rid") || typeId(r["rid"]) != QMetaType::QVariantHash) return RequestState(); QVariantHash vrid = r["rid"].toHash(); - if(!vrid.contains("sender") || vrid["sender"].type() != QVariant::ByteArray) + if(!vrid.contains("sender") || typeId(vrid["sender"]) != QMetaType::QByteArray) return RequestState(); - if(!vrid.contains("id") || vrid["id"].type() != QVariant::ByteArray) + if(!vrid.contains("id") || typeId(vrid["id"]) != QMetaType::QByteArray) return RequestState(); rs.rid = ZhttpRequest::Rid(vrid["sender"].toByteArray(), vrid["id"].toByteArray()); - if(!r.contains("in-seq") || !r["in-seq"].canConvert(QVariant::Int)) + if(!r.contains("in-seq") || !canConvert(r["in-seq"], QMetaType::Int)) return RequestState(); rs.inSeq = r["in-seq"].toInt(); - if(!r.contains("out-seq") || !r["out-seq"].canConvert(QVariant::Int)) + if(!r.contains("out-seq") || !canConvert(r["out-seq"], QMetaType::Int)) return RequestState(); rs.outSeq = r["out-seq"].toInt(); - if(!r.contains("out-credits") || !r["out-credits"].canConvert(QVariant::Int)) + if(!r.contains("out-credits") || !canConvert(r["out-credits"], QMetaType::Int)) return RequestState(); rs.outCredits = r["out-credits"].toInt(); if(r.contains("response-code")) { - if(!r["response-code"].canConvert(QVariant::Int)) + if(!canConvert(r["response-code"], QMetaType::Int)) return RequestState(); rs.responseCode = r["response-code"].toInt(); @@ -68,7 +71,7 @@ if(r.contains("peer-address")) { - if(r["peer-address"].type() != QVariant::ByteArray) + if(typeId(r["peer-address"]) != QMetaType::QByteArray) return RequestState(); if(!rs.peerAddress.setAddress(QString::fromUtf8(r["peer-address"].toByteArray()))) @@ -77,7 +80,7 @@ if(r.contains("logical-peer-address")) { - if(r["logical-peer-address"].type() != QVariant::ByteArray) + if(typeId(r["logical-peer-address"]) != QMetaType::QByteArray) return RequestState(); if(!rs.logicalPeerAddress.setAddress(QString::fromUtf8(r["logical-peer-address"].toByteArray()))) @@ -86,7 +89,7 @@ if(r.contains("https")) { - if(r["https"].type() != QVariant::Bool) + if(typeId(r["https"]) != QMetaType::Bool) return RequestState(); rs.isHttps = r["https"].toBool(); @@ -94,7 +97,7 @@ if(r.contains("debug")) { - if(r["debug"].type() != QVariant::Bool) + if(typeId(r["debug"]) != QMetaType::Bool) return RequestState(); rs.debug = r["debug"].toBool(); @@ -102,7 +105,7 @@ if(r.contains("is-retry")) { - if(r["is-retry"].type() != QVariant::Bool) + if(typeId(r["is-retry"]) != QMetaType::Bool) return RequestState(); rs.isRetry = r["is-retry"].toBool(); @@ -110,7 +113,7 @@ if(r.contains("auto-cross-origin")) { - if(r["auto-cross-origin"].type() != QVariant::Bool) + if(typeId(r["auto-cross-origin"]) != QMetaType::Bool) return RequestState(); rs.autoCrossOrigin = r["auto-cross-origin"].toBool(); @@ -118,7 +121,7 @@ if(r.contains("jsonp-callback")) { - if(r["jsonp-callback"].type() != QVariant::ByteArray) + if(typeId(r["jsonp-callback"]) != QMetaType::QByteArray) return RequestState(); rs.jsonpCallback = r["jsonp-callback"].toByteArray(); @@ -126,7 +129,7 @@ if(r.contains("jsonp-extended-response")) { - if(r["jsonp-extended-response"].type() != QVariant::Bool) + if(typeId(r["jsonp-extended-response"]) != QMetaType::Bool) return RequestState(); rs.jsonpExtendedResponse = r["jsonp-extended-response"].toBool(); @@ -134,7 +137,7 @@ if(r.contains("unreported-time")) { - if(!r["unreported-time"].canConvert(QVariant::Int)) + if(!canConvert(r["unreported-time"], QMetaType::Int)) return RequestState(); rs.unreportedTime = r["unreported-time"].toInt(); diff -Nru pushpin-1.38.0/src/cpp/handler/sessionrequest.cpp pushpin-1.39.1/src/cpp/handler/sessionrequest.cpp --- pushpin-1.38.0/src/cpp/handler/sessionrequest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/sessionrequest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -24,6 +25,7 @@ #include #include +#include "qtcompat.h" #include "zrpcmanager.h" #include "zrpcrequest.h" #include "deferred.h" @@ -100,7 +102,7 @@ if(req->success()) { QVariant vresult = req->result(); - if(vresult.type() != QVariant::List) + if(typeId(vresult) != QMetaType::QVariantList) { setFinished(false); return; @@ -111,7 +113,7 @@ QList rules; foreach(const QVariant &vr, result) { - if(vr.type() != QVariant::Hash) + if(typeId(vr) != QMetaType::QVariantHash) { setFinished(false); return; @@ -121,7 +123,7 @@ DetectRule rule; - if(!r.contains("domain") || r["domain"].type() != QVariant::ByteArray) + if(!r.contains("domain") || typeId(r["domain"]) != QMetaType::QByteArray) { setFinished(false); return; @@ -129,7 +131,7 @@ rule.domain = QString::fromUtf8(r["domain"].toByteArray()); - if(!r.contains("path-prefix") || r["path-prefix"].type() != QVariant::ByteArray) + if(!r.contains("path-prefix") || typeId(r["path-prefix"]) != QMetaType::QByteArray) { setFinished(false); return; @@ -137,7 +139,7 @@ rule.pathPrefix = r["path-prefix"].toByteArray(); - if(!r.contains("sid-ptr") || r["sid-ptr"].type() != QVariant::ByteArray) + if(!r.contains("sid-ptr") || typeId(r["sid-ptr"]) != QMetaType::QByteArray) { setFinished(false); return; @@ -147,7 +149,7 @@ if(r.contains("json-param")) { - if(r["json-param"].type() != QVariant::ByteArray) + if(typeId(r["json-param"]) != QMetaType::QByteArray) { setFinished(false); return; @@ -288,7 +290,7 @@ if(req->success()) { QVariant vresult = req->result(); - if(vresult.type() != QVariant::Hash) + if(typeId(vresult) != QMetaType::QVariantHash) { setFinished(false); return; @@ -302,7 +304,7 @@ { it.next(); const QVariant &i = it.value(); - if(i.type() != QVariant::ByteArray) + if(typeId(i) != QMetaType::QByteArray) { setFinished(false); return; diff -Nru pushpin-1.38.0/src/cpp/handler/variantutil.cpp pushpin-1.39.1/src/cpp/handler/variantutil.cpp --- pushpin-1.38.0/src/cpp/handler/variantutil.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/variantutil.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,6 +23,8 @@ #include "variantutil.h" +#include "qtcompat.h" + namespace VariantUtil { void setSuccess(bool *ok, QString *errorMessage) @@ -42,14 +45,14 @@ bool isKeyedObject(const QVariant &in) { - return (in.type() == QVariant::Hash || in.type() == QVariant::Map); + return (typeId(in) == QMetaType::QVariantHash || typeId(in) == QMetaType::QVariantMap); } QVariant createSameKeyedObject(const QVariant &in) { - if(in.type() == QVariant::Hash) + if(typeId(in) == QMetaType::QVariantHash) return QVariantHash(); - else if(in.type() == QVariant::Map) + else if(typeId(in) == QMetaType::QVariantMap) return QVariantMap(); else return QVariant(); @@ -57,9 +60,9 @@ bool keyedObjectIsEmpty(const QVariant &in) { - if(in.type() == QVariant::Hash) + if(typeId(in) == QMetaType::QVariantHash) return in.toHash().isEmpty(); - else if(in.type() == QVariant::Map) + else if(typeId(in) == QMetaType::QVariantMap) return in.toMap().isEmpty(); else return true; @@ -67,9 +70,9 @@ bool keyedObjectContains(const QVariant &in, const QString &name) { - if(in.type() == QVariant::Hash) + if(typeId(in) == QMetaType::QVariantHash) return in.toHash().contains(name); - else if(in.type() == QVariant::Map) + else if(typeId(in) == QMetaType::QVariantMap) return in.toMap().contains(name); else return false; @@ -77,9 +80,9 @@ QVariant keyedObjectGetValue(const QVariant &in, const QString &name) { - if(in.type() == QVariant::Hash) + if(typeId(in) == QMetaType::QVariantHash) return in.toHash().value(name); - else if(in.type() == QVariant::Map) + else if(typeId(in) == QMetaType::QVariantMap) return in.toMap().value(name); else return QVariant(); @@ -87,13 +90,13 @@ void keyedObjectInsert(QVariant *in, const QString &name, const QVariant &value) { - if(in->type() == QVariant::Hash) + if(typeId(*in) == QMetaType::QVariantHash) { QVariantHash h = in->toHash(); h.insert(name, value); *in = h; } - else if(in->type() == QVariant::Map) + else if(typeId(*in) == QMetaType::QVariantMap) { QVariantMap h = in->toMap(); h.insert(name, value); @@ -113,7 +116,7 @@ QString pn = !parentName.isEmpty() ? parentName : QString("object"); QVariant v; - if(in.type() == QVariant::Hash) + if(typeId(in) == QMetaType::QVariantHash) { QVariantHash h = in.toHash(); @@ -198,7 +201,7 @@ QString pn = !parentName.isEmpty() ? parentName : QString("object"); - if(v.type() != QVariant::List) + if(typeId(v) != QMetaType::QVariantList) { setError(ok, errorMessage, QString("%1 contains '%2' with wrong type").arg(pn, childName)); return QVariantList(); @@ -210,13 +213,13 @@ QString getString(const QVariant &in, bool *ok) { - if(in.type() == QVariant::String) + if(typeId(in) == QMetaType::QString) { if(ok) *ok = true; return in.toString(); } - else if(in.type() == QVariant::ByteArray) + else if(typeId(in) == QMetaType::QByteArray) { QByteArray buf = in.toByteArray(); if(ok) @@ -271,8 +274,8 @@ bool changed = false; - int type = in->type(); - if(type == QVariant::Hash) + QMetaType::Type type = typeId(*in); + if(type == QMetaType::QVariantHash) { QVariantMap vmap; QVariantHash vhash = in->toHash(); @@ -288,7 +291,7 @@ *in = vmap; changed = true; } - else if(type == QVariant::List) + else if(type == QMetaType::QVariantList) { QVariantList vlist = in->toList(); for(int n = 0; n < vlist.count(); ++n) @@ -301,7 +304,7 @@ *in = vlist; changed = true; } - else if(type == QVariant::ByteArray) + else if(type == QMetaType::QByteArray) { QByteArray buf = in->toByteArray(); if(!buf.isNull()) diff -Nru pushpin-1.38.0/src/cpp/handler/wscontrolmessage.cpp pushpin-1.39.1/src/cpp/handler/wscontrolmessage.cpp --- pushpin-1.38.0/src/cpp/handler/wscontrolmessage.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/wscontrolmessage.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-2019 Fanout, Inc. + * Copyright (C) 2024 Fanout, Inc. * * This file is part of Pushpin. * @@ -23,6 +24,7 @@ #include "wscontrolmessage.h" #include +#include "qtcompat.h" #include "variantutil.h" using namespace VariantUtil; @@ -181,9 +183,9 @@ { QVariant vcontentBin = keyedObjectGetValue(in, "content-bin"); - if(in.type() == QVariant::Map) // JSON input + if(typeId(in) == QMetaType::QVariantMap) // JSON input { - if(vcontentBin.type() != QVariant::String) + if(typeId(vcontentBin) != QMetaType::QString) { setError(ok, errorMessage, QString("%1 contains 'content-bin' with wrong type").arg(pn)); return WsControlMessage(); @@ -193,7 +195,7 @@ } else { - if(vcontentBin.type() != QVariant::ByteArray) + if(typeId(vcontentBin) != QMetaType::QByteArray) { setError(ok, errorMessage, QString("%1 contains 'content-bin' with wrong type").arg(pn)); return WsControlMessage(); @@ -208,9 +210,9 @@ else if(keyedObjectContains(in, "content")) { QVariant vcontent = keyedObjectGetValue(in, "content"); - if(vcontent.type() == QVariant::ByteArray) + if(typeId(vcontent) == QMetaType::QByteArray) out.content = vcontent.toByteArray(); - else if(vcontent.type() == QVariant::String) + else if(typeId(vcontent) == QMetaType::QString) out.content = vcontent.toString().toUtf8(); else { @@ -227,7 +229,7 @@ if(keyedObjectContains(in, "timeout")) { QVariant vtimeout = keyedObjectGetValue(in, "timeout"); - if(!vtimeout.canConvert(QVariant::Int)) + if(!canConvert(vtimeout, QMetaType::Int)) { setError(ok, errorMessage, QString("%1 contains 'timeout' with wrong type").arg(pn)); return WsControlMessage(); diff -Nru pushpin-1.38.0/src/cpp/handler/wssession.cpp pushpin-1.39.1/src/cpp/handler/wssession.cpp --- pushpin-1.38.0/src/cpp/handler/wssession.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/wssession.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -122,7 +122,7 @@ { log_debug("timing out ws session: %s", qPrintable(cid)); - emit expired(); + expired(); } void WsSession::delayedTimer_timeout() @@ -135,7 +135,7 @@ pendingRequests[reqId] = QDateTime::currentMSecsSinceEpoch() + WSCONTROL_REQUEST_TIMEOUT; setupRequestTimer(); - emit send(reqId, delayedType, message); + send(reqId, delayedType, message); } void WsSession::requestTimer_timeout() @@ -144,5 +144,5 @@ pendingRequests.clear(); setupRequestTimer(); - emit error(); + error(); } diff -Nru pushpin-1.38.0/src/cpp/handler/wssession.h pushpin-1.39.1/src/cpp/handler/wssession.h --- pushpin-1.38.0/src/cpp/handler/wssession.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/handler/wssession.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -27,6 +28,10 @@ #include #include #include "packet/httprequestdata.h" +#include + +using Signal = boost::signals2::signal; +using Connection = boost::signals2::scoped_connection; class QTimer; @@ -35,6 +40,7 @@ Q_OBJECT public: + QByteArray peer; QString cid; int nextReqId; QString channelPrefix; @@ -64,10 +70,9 @@ void sendDelayed(const QByteArray &type, const QByteArray &message, int timeout); void ack(int reqId); -signals: - void send(int reqId, const QByteArray &type, const QByteArray &message); - void expired(); - void error(); + boost::signals2::signal send; + Signal expired; + Signal error; private: void setupRequestTimer(); diff -Nru pushpin-1.38.0/src/cpp/logutil.cpp pushpin-1.39.1/src/cpp/logutil.cpp --- pushpin-1.38.0/src/cpp/logutil.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/logutil.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2017-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -24,6 +25,7 @@ #include #include +#include "qtcompat.h" #include "tnetstring.h" #include "log.h" @@ -98,7 +100,7 @@ QVariant meta; QByteArray content; - if(data.type() == QVariant::Hash) + if(typeId(data) == QMetaType::QVariantHash) { // extract content. meta is the remaining data QVariantHash hdata = data.toHash(); diff -Nru pushpin-1.38.0/src/cpp/m2adapter/m2adapterapp.cpp pushpin-1.39.1/src/cpp/m2adapter/m2adapterapp.cpp --- pushpin-1.38.0/src/cpp/m2adapter/m2adapterapp.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/m2adapter/m2adapterapp.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2013-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -32,6 +33,7 @@ #include #include "qzmqsocket.h" #include "qzmqvalve.h" +#include "qtcompat.h" #include "processquit.h" #include "tnetstring.h" #include "m2requestpacket.h" @@ -87,7 +89,7 @@ static bool validateHost(const QByteArray &in) { - for(int n = 0; n < in.count(); ++n) + for(int n = 0; n < in.size(); ++n) { if(in[n] == '/') return false; @@ -452,6 +454,10 @@ QTimer *refreshTimer; Connection quitConnection; Connection hupConnection; + map rrConnection; + Connection m2InValveConnection; + Connection zhttpInValveConnection; + Connection zwsInValveConnection; Private(M2AdapterApp *_q) : QObject(_q), @@ -653,7 +659,7 @@ } m2_in_valve = new QZmq::Valve(m2_in_sock, this); - connect(m2_in_valve, &QZmq::Valve::readyRead, this, &Private::m2_in_readyRead); + m2InValveConnection = m2_in_valve->readyRead.connect(boost::bind(&Private::m2_in_readyRead, this, boost::placeholders::_1)); m2_out_sock = new QZmq::Socket(QZmq::Socket::Pub, this); m2_out_sock->setShutdownWaitTime(0); @@ -673,7 +679,7 @@ sock->setShutdownWaitTime(0); sock->setHwm(1); // queue up 1 outstanding request at most sock->setWriteQueueEnabled(false); - connect(sock, &QZmq::Socket::readyRead, this, &Private::m2_control_readyRead); + rrConnection[sock] = sock->readyRead.connect(boost::bind(&Private::m2_control_readyRead, this, sock)); log_info("m2_control connect %s:%s", m2_send_idents[n].data(), qPrintable(spec)); sock->connectToAddress(spec); @@ -708,7 +714,7 @@ } zhttp_in_valve = new QZmq::Valve(zhttp_in_sock, this); - connect(zhttp_in_valve, &QZmq::Valve::readyRead, this, &Private::zhttp_in_readyRead); + zhttpInValveConnection = zhttp_in_valve->readyRead.connect(boost::bind(&Private::zhttp_in_readyRead, this, boost::placeholders::_1)); zhttp_out_sock = new QZmq::Socket(QZmq::Socket::Push, this); zhttp_out_sock->setShutdownWaitTime(0); @@ -777,7 +783,7 @@ } zws_in_valve = new QZmq::Valve(zws_in_sock, this); - connect(zws_in_valve, &QZmq::Valve::readyRead, this, &Private::zws_in_readyRead); + zwsInValveConnection = zws_in_valve->readyRead.connect(boost::bind(&Private::zws_in_readyRead, this, boost::placeholders::_1)); zws_out_sock = new QZmq::Socket(QZmq::Socket::Push, this); zws_out_sock->setShutdownWaitTime(0); @@ -1269,7 +1275,7 @@ log_debug("m2: IN control %s %s", m2_send_idents[index].data(), qPrintable(TnetString::variantToString(data))); #endif - if(data.type() != QVariant::Hash) + if(typeId(data) != QMetaType::QVariantHash) return; QVariantHash vhash = data.toHash(); @@ -1289,7 +1295,7 @@ QSet ids; foreach(const QVariant &row, rows.toList()) { - if(row.type() != QVariant::List) + if(typeId(row) != QMetaType::QVariantList) break; QVariantList vlist = row.toList(); @@ -2313,7 +2319,6 @@ } } -private slots: void m2_in_readyRead(const QList &message) { if(message.count() != 1) @@ -2733,9 +2738,8 @@ } } - void m2_control_readyRead() + void m2_control_readyRead(QZmq::Socket *sock) { - QZmq::Socket *sock = (QZmq::Socket *)sender(); int index = -1; for(int n = 0; n < controlPorts.count(); ++n) { @@ -2820,6 +2824,7 @@ handleZhttpIn(WebSocket, message); } +private slots: void status_timeout() { int now = time.elapsed(); diff -Nru pushpin-1.38.0/src/cpp/m2adapter/m2adapterapp.h pushpin-1.39.1/src/cpp/m2adapter/m2adapterapp.h --- pushpin-1.38.0/src/cpp/m2adapter/m2adapterapp.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/m2adapter/m2adapterapp.h 2024-03-18 18:42:24.000000000 +0000 @@ -26,6 +26,7 @@ #include #include +using std::map; using SignalInt = boost::signals2::signal; using Connection = boost::signals2::scoped_connection; diff -Nru pushpin-1.38.0/src/cpp/m2adapter/m2requestpacket.cpp pushpin-1.39.1/src/cpp/m2adapter/m2requestpacket.cpp --- pushpin-1.38.0/src/cpp/m2adapter/m2requestpacket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/m2adapter/m2requestpacket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2013 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -25,6 +26,7 @@ #include #include #include +#include "qtcompat.h" #include "tnetstring.h" static bool isAllCaps(const QString &s) @@ -119,7 +121,7 @@ QString key = vit.key(); QVariant val = vit.value(); - if(val.type() == QVariant::ByteArray) + if(typeId(val) == QMetaType::QByteArray) { QByteArray ba = val.toByteArray(); @@ -128,13 +130,13 @@ if(!isAllCaps(key) && !skipHeaders.contains(key)) headers += HttpHeader(makeMixedCaseHeader(key).toLatin1(), ba); } - else if(val.type() == QVariant::List) + else if(typeId(val) == QMetaType::QVariantList) { QVariantList vl = val.toList(); if(vl.isEmpty()) return false; - if(vl[0].type() != QVariant::ByteArray) + if(typeId(vl[0]) != QMetaType::QByteArray) return false; m2headers[key] = vl[0].toByteArray(); @@ -145,7 +147,7 @@ foreach(const QVariant &v, vl) { - if(v.type() != QVariant::ByteArray) + if(typeId(v) != QMetaType::QByteArray) return false; headers += HttpHeader(name, v.toByteArray()); @@ -172,7 +174,7 @@ QString key = vit.key(); QVariant val = vit.value(); - if(val.type() == QVariant::String) + if(typeId(val) == QMetaType::QString) { QByteArray ba = val.toString().toUtf8(); @@ -181,13 +183,13 @@ if(!isAllCaps(key) && !skipHeaders.contains(key)) headers += HttpHeader(makeMixedCaseHeader(key).toLatin1(), ba); } - else if(val.type() == QVariant::List) + else if(typeId(val) == QMetaType::QVariantList) { QVariantList vl = val.toList(); if(vl.isEmpty()) return false; - if(vl[0].type() != QVariant::String) + if(typeId(vl[0]) != QMetaType::QString) return false; m2headers[key] = vl[0].toString().toUtf8(); @@ -198,7 +200,7 @@ foreach(const QVariant &v, vl) { - if(v.type() != QVariant::String) + if(typeId(v) != QMetaType::QString) return false; headers += HttpHeader(name, v.toString().toUtf8()); @@ -238,7 +240,7 @@ return false; QVariantMap data = doc.object().toVariantMap(); - if(!data.contains("type") || data["type"].type() != QVariant::String) + if(!data.contains("type") || typeId(data["type"]) != QMetaType::QString) return false; QString jtype = data["type"].toString(); diff -Nru pushpin-1.38.0/src/cpp/packet/retryrequestpacket.cpp pushpin-1.39.1/src/cpp/packet/retryrequestpacket.cpp --- pushpin-1.38.0/src/cpp/packet/retryrequestpacket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/packet/retryrequestpacket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2012-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,6 +23,8 @@ #include "retryrequestpacket.h" +#include "qtcompat.h" + RetryRequestPacket::RetryRequestPacket() : haveInspectInfo(false), retrySeq(-1) @@ -140,37 +142,37 @@ bool RetryRequestPacket::fromVariant(const QVariant &in) { - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) return false; QVariantHash obj = in.toHash(); - if(!obj.contains("requests") || obj["requests"].type() != QVariant::List) + if(!obj.contains("requests") || typeId(obj["requests"]) != QMetaType::QVariantList) return false; requests.clear(); foreach(const QVariant &i, obj["requests"].toList()) { - if(i.type() != QVariant::Hash) + if(typeId(i) != QMetaType::QVariantHash) return false; QVariantHash vrequest = i.toHash(); Request r; - if(!vrequest.contains("rid") || vrequest["rid"].type() != QVariant::Hash) + if(!vrequest.contains("rid") || typeId(vrequest["rid"]) != QMetaType::QVariantHash) return false; QVariantHash vrid = vrequest["rid"].toHash(); QByteArray sender, id; - if(!vrid.contains("sender") || vrid["sender"].type() != QVariant::ByteArray) + if(!vrid.contains("sender") || typeId(vrid["sender"]) != QMetaType::QByteArray) return false; sender = vrid["sender"].toByteArray(); - if(!vrid.contains("id") || vrid["id"].type() != QVariant::ByteArray) + if(!vrid.contains("id") || typeId(vrid["id"]) != QMetaType::QByteArray) return false; id = vrid["id"].toByteArray(); @@ -179,7 +181,7 @@ if(vrequest.contains("https")) { - if(vrequest["https"].type() != QVariant::Bool) + if(typeId(vrequest["https"]) != QMetaType::Bool) return false; r.https = vrequest["https"].toBool(); @@ -187,7 +189,7 @@ if(vrequest.contains("peer-address")) { - if(vrequest["peer-address"].type() != QVariant::ByteArray) + if(typeId(vrequest["peer-address"]) != QMetaType::QByteArray) return false; r.peerAddress = QHostAddress(QString::fromUtf8(vrequest["peer-address"].toByteArray())); @@ -195,7 +197,7 @@ if(vrequest.contains("debug")) { - if(vrequest["debug"].type() != QVariant::Bool) + if(typeId(vrequest["debug"]) != QMetaType::Bool) return false; r.debug = vrequest["debug"].toBool(); @@ -203,7 +205,7 @@ if(vrequest.contains("auto-cross-origin")) { - if(vrequest["auto-cross-origin"].type() != QVariant::Bool) + if(typeId(vrequest["auto-cross-origin"]) != QMetaType::Bool) return false; r.autoCrossOrigin = vrequest["auto-cross-origin"].toBool(); @@ -211,14 +213,14 @@ if(vrequest.contains("jsonp-callback")) { - if(vrequest["jsonp-callback"].type() != QVariant::ByteArray) + if(typeId(vrequest["jsonp-callback"]) != QMetaType::QByteArray) return false; r.jsonpCallback = vrequest["jsonp-callback"].toByteArray(); if(vrequest.contains("jsonp-extended-response")) { - if(vrequest["jsonp-extended-response"].type() != QVariant::Bool) + if(typeId(vrequest["jsonp-extended-response"]) != QMetaType::Bool) return false; r.jsonpExtendedResponse = vrequest["jsonp-extended-response"].toBool(); @@ -227,21 +229,21 @@ if(vrequest.contains("unreported-time")) { - if(!vrequest["unreported-time"].canConvert(QVariant::Int)) + if(!canConvert(vrequest["unreported-time"], QMetaType::Int)) return false; r.unreportedTime = vrequest["unreported-time"].toInt(); } - if(!vrequest.contains("in-seq") || !vrequest["in-seq"].canConvert(QVariant::Int)) + if(!vrequest.contains("in-seq") || !canConvert(vrequest["in-seq"], QMetaType::Int)) return false; r.inSeq = vrequest["in-seq"].toInt(); - if(!vrequest.contains("out-seq") || !vrequest["out-seq"].canConvert(QVariant::Int)) + if(!vrequest.contains("out-seq") || !canConvert(vrequest["out-seq"], QMetaType::Int)) return false; r.outSeq = vrequest["out-seq"].toInt(); - if(!vrequest.contains("out-credits") || !vrequest["out-credits"].canConvert(QVariant::Int)) + if(!vrequest.contains("out-credits") || !canConvert(vrequest["out-credits"], QMetaType::Int)) return false; r.outCredits = vrequest["out-credits"].toInt(); @@ -251,22 +253,22 @@ requests += r; } - if(!obj.contains("request-data") || obj["request-data"].type() != QVariant::Hash) + if(!obj.contains("request-data") || typeId(obj["request-data"]) != QMetaType::QVariantHash) return false; QVariantHash vrequestData = obj["request-data"].toHash(); - if(!vrequestData.contains("method") || vrequestData["method"].type() != QVariant::ByteArray) + if(!vrequestData.contains("method") || typeId(vrequestData["method"]) != QMetaType::QByteArray) return false; requestData.method = QString::fromLatin1(vrequestData["method"].toByteArray()); - if(!vrequestData.contains("uri") || vrequestData["uri"].type() != QVariant::ByteArray) + if(!vrequestData.contains("uri") || typeId(vrequestData["uri"]) != QMetaType::QByteArray) return false; requestData.uri = QUrl::fromEncoded(vrequestData["uri"].toByteArray(), QUrl::StrictMode); requestData.headers.clear(); if(vrequestData.contains("headers")) { - if(vrequestData["headers"].type() != QVariant::List) + if(typeId(vrequestData["headers"]) != QMetaType::QVariantList) return false; foreach(const QVariant &i, vrequestData["headers"].toList()) @@ -275,31 +277,31 @@ if(list.count() != 2) return false; - if(list[0].type() != QVariant::ByteArray || list[1].type() != QVariant::ByteArray) + if(typeId(list[0]) != QMetaType::QByteArray || typeId(list[1]) != QMetaType::QByteArray) return false; requestData.headers += QPair(list[0].toByteArray(), list[1].toByteArray()); } } - if(!vrequestData.contains("body") || vrequestData["body"].type() != QVariant::ByteArray) + if(!vrequestData.contains("body") || typeId(vrequestData["body"]) != QMetaType::QByteArray) return false; requestData.body = vrequestData["body"].toByteArray(); if(obj.contains("inspect")) { - if(obj["inspect"].type() != QVariant::Hash) + if(typeId(obj["inspect"]) != QMetaType::QVariantHash) return false; QVariantHash vinspect = obj["inspect"].toHash(); - if(!vinspect.contains("no-proxy") || vinspect["no-proxy"].type() != QVariant::Bool) + if(!vinspect.contains("no-proxy") || typeId(vinspect["no-proxy"]) != QMetaType::Bool) return false; inspectInfo.doProxy = !vinspect["no-proxy"].toBool(); inspectInfo.sharingKey.clear(); if(vinspect.contains("sharing-key")) { - if(vinspect["sharing-key"].type() != QVariant::ByteArray) + if(typeId(vinspect["sharing-key"]) != QMetaType::QByteArray) return false; inspectInfo.sharingKey = vinspect["sharing-key"].toByteArray(); @@ -307,7 +309,7 @@ if(vinspect.contains("sid")) { - if(vinspect["sid"].type() != QVariant::ByteArray) + if(typeId(vinspect["sid"]) != QMetaType::QByteArray) return false; inspectInfo.sid = vinspect["sid"].toByteArray(); @@ -315,7 +317,7 @@ if(vinspect.contains("last-ids")) { - if(vinspect["last-ids"].type() != QVariant::Hash) + if(typeId(vinspect["last-ids"]) != QMetaType::QVariantHash) return false; QVariantHash vlastIds = vinspect["last-ids"].toHash(); @@ -324,7 +326,7 @@ { it.next(); - if(it.value().type() != QVariant::ByteArray) + if(typeId(it.value()) != QMetaType::QByteArray) return false; QByteArray key = it.key().toUtf8(); @@ -340,7 +342,7 @@ if(obj.contains("route")) { - if(obj["route"].type() != QVariant::ByteArray) + if(typeId(obj["route"]) != QMetaType::QByteArray) return false; route = obj["route"].toByteArray(); @@ -348,7 +350,7 @@ if(obj.contains("retry-seq")) { - if(!obj["retry-seq"].canConvert(QVariant::Int)) + if(!canConvert(obj["retry-seq"], QMetaType::Int)) return false; retrySeq = obj["retry-seq"].toInt(); diff -Nru pushpin-1.38.0/src/cpp/packet/statspacket.cpp pushpin-1.39.1/src/cpp/packet/statspacket.cpp --- pushpin-1.38.0/src/cpp/packet/statspacket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/packet/statspacket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2014-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,13 +23,13 @@ #include "statspacket.h" -#include +#include "qtcompat.h" static bool tryGetInt(const QVariantHash &obj, const QString &name, int *result) { if(obj.contains(name)) { - if(!obj[name].canConvert(QVariant::Int)) + if(!canConvert(obj[name], QMetaType::Int)) return false; *result = obj[name].toInt(); @@ -176,14 +176,14 @@ bool StatsPacket::fromVariant(const QByteArray &_type, const QVariant &in) { - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) return false; QVariantHash obj = in.toHash(); if(obj.contains("from")) { - if(obj["from"].type() != QVariant::ByteArray) + if(typeId(obj["from"]) != QMetaType::QByteArray) return false; from = obj["from"].toByteArray(); @@ -191,7 +191,7 @@ if(obj.contains("route")) { - if(obj["route"].type() != QVariant::ByteArray) + if(typeId(obj["route"]) != QMetaType::QByteArray) return false; route = obj["route"].toByteArray(); @@ -201,7 +201,7 @@ { type = Activity; - if(!obj.contains("count") || !obj["count"].canConvert(QVariant::Int)) + if(!obj.contains("count") || !canConvert(obj["count"], QMetaType::Int)) return false; count = obj["count"].toInt(); @@ -212,20 +212,20 @@ { type = Message; - if(!obj.contains("channel") || obj["channel"].type() != QVariant::ByteArray) + if(!obj.contains("channel") || typeId(obj["channel"]) != QMetaType::QByteArray) return false; channel = obj["channel"].toByteArray(); if(obj.contains("item-id")) { - if(obj["item-id"].type() != QVariant::ByteArray) + if(typeId(obj["item-id"]) != QMetaType::QByteArray) return false; itemId = obj["item-id"].toByteArray(); } - if(!obj.contains("count") || !obj["count"].canConvert(QVariant::Int)) + if(!obj.contains("count") || !canConvert(obj["count"], QMetaType::Int)) return false; count = obj["count"].toInt(); @@ -234,20 +234,20 @@ if(obj.contains("blocks")) { - if(!obj["blocks"].canConvert(QVariant::Int)) + if(!canConvert(obj["blocks"], QMetaType::Int)) return false; blocks = obj["blocks"].toInt(); } - if(!obj.contains("transport") || obj["transport"].type() != QVariant::ByteArray) + if(!obj.contains("transport") || typeId(obj["transport"]) != QMetaType::QByteArray) return false; transport = obj["transport"].toByteArray(); } else if(_type == "conn") { - if(!obj.contains("id") || obj["id"].type() != QVariant::ByteArray) + if(!obj.contains("id") || typeId(obj["id"]) != QMetaType::QByteArray) return false; connectionId = obj["id"].toByteArray(); @@ -255,7 +255,7 @@ type = Connected; if(obj.contains("unavailable")) { - if(obj["unavailable"].type() != QVariant::Bool) + if(typeId(obj["unavailable"]) != QMetaType::Bool) return false; if(obj["unavailable"].toBool()) @@ -264,7 +264,7 @@ if(type == Connected) { - if(!obj.contains("type") || obj["type"].type() != QVariant::ByteArray) + if(!obj.contains("type") || typeId(obj["type"]) != QMetaType::QByteArray) return false; QByteArray typeStr = obj["type"].toByteArray(); @@ -277,7 +277,7 @@ if(obj.contains("peer-address")) { - if(obj["peer-address"].type() != QVariant::ByteArray) + if(typeId(obj["peer-address"]) != QMetaType::QByteArray) return false; QByteArray peerAddressStr = obj["peer-address"].toByteArray(); @@ -287,13 +287,13 @@ if(obj.contains("ssl")) { - if(obj["ssl"].type() != QVariant::Bool) + if(typeId(obj["ssl"]) != QMetaType::Bool) return false; ssl = obj["ssl"].toBool(); } - if(!obj.contains("ttl") || !obj["ttl"].canConvert(QVariant::Int)) + if(!obj.contains("ttl") || !canConvert(obj["ttl"], QMetaType::Int)) return false; ttl = obj["ttl"].toInt(); @@ -303,12 +303,12 @@ } else if(_type == "sub") { - if(!obj.contains("mode") || obj["mode"].type() != QVariant::ByteArray) + if(!obj.contains("mode") || typeId(obj["mode"]) != QMetaType::QByteArray) return false; mode = obj["mode"].toByteArray(); - if(!obj.contains("channel") || obj["channel"].type() != QVariant::ByteArray) + if(!obj.contains("channel") || typeId(obj["channel"]) != QMetaType::QByteArray) return false; channel = obj["channel"].toByteArray(); @@ -316,7 +316,7 @@ type = Subscribed; if(obj.contains("unavailable")) { - if(obj["unavailable"].type() != QVariant::Bool) + if(typeId(obj["unavailable"]) != QMetaType::Bool) return false; if(obj["unavailable"].toBool()) @@ -325,7 +325,7 @@ if(type == Subscribed) { - if(!obj.contains("ttl") || !obj["ttl"].canConvert(QVariant::Int)) + if(!obj.contains("ttl") || !canConvert(obj["ttl"], QMetaType::Int)) return false; ttl = obj["ttl"].toInt(); @@ -334,7 +334,7 @@ if(obj.contains("subscribers")) { - if(!obj["subscribers"].canConvert(QVariant::Int)) + if(!canConvert(obj["subscribers"], QMetaType::Int)) return false; subscribers = obj["subscribers"].toInt(); @@ -349,7 +349,7 @@ if(obj.contains("connections")) { - if(!obj["connections"].canConvert(QVariant::Int)) + if(!canConvert(obj["connections"], QMetaType::Int)) return false; connectionsMax = obj["connections"].toInt(); @@ -357,7 +357,7 @@ if(obj.contains("minutes")) { - if(!obj["minutes"].canConvert(QVariant::Int)) + if(!canConvert(obj["minutes"], QMetaType::Int)) return false; connectionsMinutes = obj["minutes"].toInt(); @@ -365,7 +365,7 @@ if(obj.contains("received")) { - if(!obj["received"].canConvert(QVariant::Int)) + if(!canConvert(obj["received"], QMetaType::Int)) return false; messagesReceived = obj["received"].toInt(); @@ -373,7 +373,7 @@ if(obj.contains("sent")) { - if(!obj["sent"].canConvert(QVariant::Int)) + if(!canConvert(obj["sent"], QMetaType::Int)) return false; messagesSent = obj["sent"].toInt(); @@ -381,7 +381,7 @@ if(obj.contains("http-response-sent")) { - if(!obj["http-response-sent"].canConvert(QVariant::Int)) + if(!canConvert(obj["http-response-sent"], QMetaType::Int)) return false; httpResponseMessagesSent = obj["http-response-sent"].toInt(); @@ -389,7 +389,7 @@ if(obj.contains("blocks-received")) { - if(!obj["blocks-received"].canConvert(QVariant::Int)) + if(!canConvert(obj["blocks-received"], QMetaType::Int)) return false; blocksReceived = obj["blocks-received"].toInt(); @@ -397,7 +397,7 @@ if(obj.contains("blocks-sent")) { - if(!obj["blocks-sent"].canConvert(QVariant::Int)) + if(!canConvert(obj["blocks-sent"], QMetaType::Int)) return false; blocksSent = obj["blocks-sent"].toInt(); @@ -405,7 +405,7 @@ if(obj.contains("duration")) { - if(!obj["duration"].canConvert(QVariant::Int)) + if(!canConvert(obj["duration"], QMetaType::Int)) return false; duration = obj["duration"].toInt(); @@ -442,7 +442,7 @@ if(obj.contains("requests-received")) { - if(!obj["requests-received"].canConvert(QVariant::Int)) + if(!canConvert(obj["requests-received"], QMetaType::Int)) return false; int x = obj["requests-received"].toInt(); @@ -456,7 +456,7 @@ { type = ConnectionsMax; - if(!obj.contains("max") || !obj["max"].canConvert(QVariant::Int)) + if(!obj.contains("max") || !canConvert(obj["max"], QMetaType::Int)) return false; int x = obj["max"].toInt(); @@ -465,7 +465,7 @@ connectionsMax = x; - if(!obj.contains("ttl") || !obj["ttl"].canConvert(QVariant::Int)) + if(!obj.contains("ttl") || !canConvert(obj["ttl"], QMetaType::Int)) return false; x = obj["ttl"].toInt(); @@ -476,7 +476,7 @@ if(obj.contains("retry-seq")) { - if(!obj["retry-seq"].canConvert(QVariant::LongLong)) + if(!canConvert(obj["retry-seq"], QMetaType::LongLong)) return false; int x = obj["retry-seq"].toLongLong(); diff -Nru pushpin-1.38.0/src/cpp/packet/wscontrolpacket.cpp pushpin-1.39.1/src/cpp/packet/wscontrolpacket.cpp --- pushpin-1.38.0/src/cpp/packet/wscontrolpacket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/packet/wscontrolpacket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,6 +24,7 @@ #include "wscontrolpacket.h" #include +#include "qtcompat.h" // FIXME: rewrite packet class using this code? /*class WsControlPacket @@ -153,6 +155,8 @@ { QVariantHash obj; + obj["from"] = from; + QVariantList vitems; foreach(const Item &item, items) { @@ -172,6 +176,7 @@ case Item::Send: typeStr = "send"; break; case Item::NeedKeepAlive: typeStr = "need-keep-alive"; break; case Item::Subscribe: typeStr = "subscribe"; break; + case Item::Refresh: typeStr = "refresh"; break; case Item::Close: typeStr = "close"; break; case Item::Detach: typeStr = "detach"; break; case Item::Ack: typeStr = "ack"; break; @@ -231,30 +236,36 @@ bool WsControlPacket::fromVariant(const QVariant &in) { - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) return false; QVariantHash obj = in.toHash(); - if(!obj.contains("items") || obj["items"].type() != QVariant::List) + if(!obj.contains("from") || typeId(obj["from"]) != QMetaType::QByteArray) return false; + + from = obj["from"].toByteArray(); + + if(!obj.contains("items") || typeId(obj["items"]) != QMetaType::QVariantList) + return false; + QVariantList vitems = obj["items"].toList(); items.clear(); foreach(const QVariant &v, vitems) { - if(v.type() != QVariant::Hash) + if(typeId(v) != QMetaType::QVariantHash) return false; QVariantHash vitem = v.toHash(); Item item; - if(!vitem.contains("cid") || vitem["cid"].type() != QVariant::ByteArray) + if(!vitem.contains("cid") || typeId(vitem["cid"]) != QMetaType::QByteArray) return false; item.cid = vitem["cid"].toByteArray(); - if(!vitem.contains("type") || vitem["type"].type() != QVariant::ByteArray) + if(!vitem.contains("type") || typeId(vitem["type"]) != QMetaType::QByteArray) return false; QByteArray typeStr = vitem["type"].toByteArray(); @@ -276,6 +287,8 @@ item.type = Item::NeedKeepAlive; else if(typeStr == "subscribe") item.type = Item::Subscribe; + else if(typeStr == "refresh") + item.type = Item::Refresh; else if(typeStr == "close") item.type = Item::Close; else if(typeStr == "detach") @@ -287,7 +300,7 @@ if(vitem.contains("req-id")) { - if(vitem["req-id"].type() != QVariant::ByteArray) + if(typeId(vitem["req-id"]) != QMetaType::QByteArray) return false; item.requestId = vitem["req-id"].toByteArray(); @@ -295,7 +308,7 @@ if(vitem.contains("uri")) { - if(vitem["uri"].type() != QVariant::ByteArray) + if(typeId(vitem["uri"]) != QMetaType::QByteArray) return false; item.uri = QUrl::fromEncoded(vitem["uri"].toByteArray(), QUrl::StrictMode); @@ -303,7 +316,7 @@ if(vitem.contains("content-type")) { - if(vitem["content-type"].type() != QVariant::ByteArray) + if(typeId(vitem["content-type"]) != QMetaType::QByteArray) return false; QByteArray contentType = vitem["content-type"].toByteArray(); @@ -313,7 +326,7 @@ if(vitem.contains("message")) { - if(vitem["message"].type() != QVariant::ByteArray) + if(typeId(vitem["message"]) != QMetaType::QByteArray) return false; item.message = vitem["message"].toByteArray(); @@ -321,7 +334,7 @@ if(vitem.contains("queue")) { - if(vitem["queue"].type() != QVariant::Bool) + if(typeId(vitem["queue"]) != QMetaType::Bool) return false; item.queue = vitem["queue"].toBool(); @@ -329,7 +342,7 @@ if(vitem.contains("code")) { - if(!vitem["code"].canConvert(QVariant::Int)) + if(!canConvert(vitem["code"], QMetaType::Int)) return false; item.code = vitem["code"].toInt(); @@ -337,7 +350,7 @@ if(vitem.contains("reason")) { - if(vitem["reason"].type() != QVariant::ByteArray) + if(typeId(vitem["reason"]) != QMetaType::QByteArray) return false; item.reason = vitem["reason"].toByteArray(); @@ -345,7 +358,7 @@ if(vitem.contains("route")) { - if(vitem["route"].type() != QVariant::ByteArray) + if(typeId(vitem["route"]) != QMetaType::QByteArray) return false; QByteArray route = vitem["route"].toByteArray(); @@ -355,7 +368,7 @@ if(vitem.contains("separate-stats")) { - if(vitem["separate-stats"].type() != QVariant::Bool) + if(typeId(vitem["separate-stats"]) != QMetaType::Bool) return false; item.separateStats = vitem["separate-stats"].toBool(); @@ -363,7 +376,7 @@ if(vitem.contains("channel-prefix")) { - if(vitem["channel-prefix"].type() != QVariant::ByteArray) + if(typeId(vitem["channel-prefix"]) != QMetaType::QByteArray) return false; QByteArray channelPrefix = vitem["channel-prefix"].toByteArray(); @@ -373,7 +386,7 @@ if(vitem.contains("channel")) { - if(vitem["channel"].type() != QVariant::ByteArray) + if(typeId(vitem["channel"]) != QMetaType::QByteArray) return false; QByteArray channel = vitem["channel"].toByteArray(); @@ -383,7 +396,7 @@ if(vitem.contains("ttl")) { - if(!vitem["ttl"].canConvert(QVariant::Int)) + if(!canConvert(vitem["ttl"], QMetaType::Int)) return false; item.ttl = vitem["ttl"].toInt(); @@ -393,7 +406,7 @@ if(vitem.contains("timeout")) { - if(!vitem["timeout"].canConvert(QVariant::Int)) + if(!canConvert(vitem["timeout"], QMetaType::Int)) return false; item.timeout = vitem["timeout"].toInt(); @@ -403,7 +416,7 @@ if(vitem.contains("keep-alive-mode")) { - if(!vitem["keep-alive-mode"].canConvert(QVariant::ByteArray)) + if(!canConvert(vitem["keep-alive-mode"], QMetaType::QByteArray)) return false; QByteArray keepAliveMode = vitem["keep-alive-mode"].toByteArray(); diff -Nru pushpin-1.38.0/src/cpp/packet/wscontrolpacket.h pushpin-1.39.1/src/cpp/packet/wscontrolpacket.h --- pushpin-1.38.0/src/cpp/packet/wscontrolpacket.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/packet/wscontrolpacket.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -45,6 +46,7 @@ Cancel, Send, KeepAliveSetup, + Refresh, Close, Detach, Ack @@ -78,6 +80,7 @@ } }; + QByteArray from; QList items; QVariant toVariant() const; diff -Nru pushpin-1.38.0/src/cpp/packet/zrpcrequestpacket.cpp pushpin-1.39.1/src/cpp/packet/zrpcrequestpacket.cpp --- pushpin-1.38.0/src/cpp/packet/zrpcrequestpacket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/packet/zrpcrequestpacket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,10 +23,15 @@ #include "zrpcrequestpacket.h" +#include "qtcompat.h" + QVariant ZrpcRequestPacket::toVariant() const { QVariantHash obj; + if(!from.isEmpty()) + obj["from"] = from; + if(!id.isEmpty()) obj["id"] = id; @@ -39,26 +45,34 @@ bool ZrpcRequestPacket::fromVariant(const QVariant &in) { - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) return false; QVariantHash obj = in.toHash(); + if(obj.contains("from")) + { + if(typeId(obj["from"]) != QMetaType::QByteArray) + return false; + + from = obj["from"].toByteArray(); + } + if(obj.contains("id")) { - if(obj["id"].type() != QVariant::ByteArray) + if(typeId(obj["id"]) != QMetaType::QByteArray) return false; id = obj["id"].toByteArray(); } - if(!obj.contains("method") || obj["method"].type() != QVariant::ByteArray) + if(!obj.contains("method") || typeId(obj["method"]) != QMetaType::QByteArray) return false; method = QString::fromUtf8(obj["method"].toByteArray()); if(obj.contains("args")) { - if(obj["args"].type() != QVariant::Hash) + if(typeId(obj["args"]) != QMetaType::QVariantHash) return false; args = obj["args"].toHash(); diff -Nru pushpin-1.38.0/src/cpp/packet/zrpcrequestpacket.h pushpin-1.39.1/src/cpp/packet/zrpcrequestpacket.h --- pushpin-1.38.0/src/cpp/packet/zrpcrequestpacket.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/packet/zrpcrequestpacket.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -29,6 +30,7 @@ class ZrpcRequestPacket { public: + QByteArray from; QByteArray id; QString method; QVariantHash args; diff -Nru pushpin-1.38.0/src/cpp/packet/zrpcresponsepacket.cpp pushpin-1.39.1/src/cpp/packet/zrpcresponsepacket.cpp --- pushpin-1.38.0/src/cpp/packet/zrpcresponsepacket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/packet/zrpcresponsepacket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -22,6 +23,8 @@ #include "zrpcresponsepacket.h" +#include "qtcompat.h" + QVariant ZrpcResponsePacket::toVariant() const { QVariantHash obj; @@ -33,7 +36,7 @@ if(success) { - if(value.type() == QVariant::String) + if(typeId(value) == QMetaType::QString) obj["value"] = value.toString().toUtf8(); else obj["value"] = value; @@ -44,7 +47,7 @@ if(value.isValid()) { - if(value.type() == QVariant::String) + if(typeId(value) == QMetaType::QString) obj["value"] = value.toString().toUtf8(); else obj["value"] = value; @@ -56,20 +59,20 @@ bool ZrpcResponsePacket::fromVariant(const QVariant &in) { - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) return false; QVariantHash obj = in.toHash(); if(obj.contains("id")) { - if(obj["id"].type() != QVariant::ByteArray) + if(typeId(obj["id"]) != QMetaType::QByteArray) return false; id = obj["id"].toByteArray(); } - if(!obj.contains("success") || obj["success"].type() != QVariant::Bool) + if(!obj.contains("success") || typeId(obj["success"]) != QMetaType::Bool) return false; success = obj["success"].toBool(); @@ -83,7 +86,7 @@ } else { - if(!obj.contains("condition") || obj["condition"].type() != QVariant::ByteArray) + if(!obj.contains("condition") || typeId(obj["condition"]) != QMetaType::QByteArray) return false; condition = obj["condition"].toByteArray(); diff -Nru pushpin-1.38.0/src/cpp/processquit.cpp pushpin-1.39.1/src/cpp/processquit.cpp --- pushpin-1.38.0/src/cpp/processquit.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/processquit.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -60,7 +60,7 @@ QObject(parent) { sn = new QSocketNotifier(socket, type, this); - connect(sn, SIGNAL(activated(int)), SLOT(doActivated())); + connect(sn, &QSocketNotifier::activated, this, &SafeSocketNotifier::doActivated); } ~SafeSocketNotifier() @@ -82,10 +82,9 @@ private: QSocketNotifier *sn; -private slots: - void doActivated() + void doActivated(int sock) { - activated(sn->socket()); + activated(sock); } }; @@ -157,6 +156,7 @@ unixWatchRemove(SIGINT); unixWatchRemove(SIGHUP); unixWatchRemove(SIGTERM); + activatedConnection.disconnect(); delete sig_notifier; close(sig_pipe[0]); close(sig_pipe[1]); diff -Nru pushpin-1.38.0/src/cpp/proxy/acceptrequest.cpp pushpin-1.39.1/src/cpp/proxy/acceptrequest.cpp --- pushpin-1.38.0/src/cpp/proxy/acceptrequest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/acceptrequest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2015-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,6 +23,7 @@ #include "acceptrequest.h" +#include "qtcompat.h" #include "acceptdata.h" static QVariant acceptDataToVariant(const AcceptData &adata) @@ -220,7 +221,7 @@ { AcceptRequest::ResponseData out; - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) { *ok = false; return AcceptRequest::ResponseData(); @@ -230,7 +231,7 @@ if(obj.contains("accepted")) { - if(obj["accepted"].type() != QVariant::Bool) + if(typeId(obj["accepted"]) != QMetaType::Bool) { *ok = false; return AcceptRequest::ResponseData(); @@ -241,7 +242,7 @@ if(obj.contains("response")) { - if(obj["response"].type() != QVariant::Hash) + if(typeId(obj["response"]) != QMetaType::QVariantHash) { *ok = false; return AcceptRequest::ResponseData(); @@ -251,7 +252,7 @@ if(vresponse.contains("code")) { - if(!vresponse["code"].canConvert(QVariant::Int)) + if(!canConvert(vresponse["code"], QMetaType::Int)) { *ok = false; return AcceptRequest::ResponseData(); @@ -262,7 +263,7 @@ if(vresponse.contains("reason")) { - if(vresponse["reason"].type() != QVariant::ByteArray) + if(typeId(vresponse["reason"]) != QMetaType::QByteArray) { *ok = false; return AcceptRequest::ResponseData(); @@ -273,7 +274,7 @@ if(vresponse.contains("headers")) { - if(vresponse["headers"].type() != QVariant::List) + if(typeId(vresponse["headers"]) != QMetaType::QVariantList) { *ok = false; return AcceptRequest::ResponseData(); @@ -288,7 +289,7 @@ return AcceptRequest::ResponseData(); } - if(list[0].type() != QVariant::ByteArray || list[1].type() != QVariant::ByteArray) + if(typeId(list[0]) != QMetaType::QByteArray || typeId(list[1]) != QMetaType::QByteArray) { *ok = false; return AcceptRequest::ResponseData(); @@ -300,7 +301,7 @@ if(vresponse.contains("body")) { - if(vresponse["body"].type() != QVariant::ByteArray) + if(typeId(vresponse["body"]) != QMetaType::QByteArray) { *ok = false; return AcceptRequest::ResponseData(); diff -Nru pushpin-1.38.0/src/cpp/proxy/app.cpp pushpin-1.39.1/src/cpp/proxy/app.cpp --- pushpin-1.38.0/src/cpp/proxy/app.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/app.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2012-2022 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -30,12 +30,16 @@ #include #include #include "processquit.h" +#include "rtimer.h" #include "log.h" #include "settings.h" #include "xffrule.h" +#include "domainmap.h" #include "engine.h" #include "config.h" +using Connection = boost::signals2::scoped_connection; + static void trimlist(QStringList *list) { for(int n = 0; n < list->count(); ++n) @@ -68,6 +72,22 @@ return out; } +static QString suffixSpec(const QString &s, int i) +{ + if(s.startsWith("ipc:")) + return s + QString("-%1").arg(i); + + return s; +} + +static QStringList suffixSpecs(const QStringList &l, int i) +{ + if(l.count() == 1 && l[0].startsWith("ipc:")) + return QStringList() << (l[0] + QString("-%1").arg(i)); + + return l; +} + enum CommandLineParseResult { CommandLineOk, @@ -162,6 +182,158 @@ return CommandLineOk; } +class EngineWorker : public QObject +{ + Q_OBJECT + +public: + EngineWorker(const Engine::Configuration &config, DomainMap *domainMap) : + QObject(), + config_(config), + engine_(new Engine(domainMap, this)) + { + } + + Signal started; + Signal stopped; + Signal error; + +public slots: + void start() + { + if(!engine_->start(config_)) + { + delete engine_; + engine_ = 0; + + error(); + return; + } + + started(); + } + + void stop() + { + delete engine_; + engine_ = 0; + + stopped(); + } + + void routesChanged() + { + if(engine_) + engine_->routesChanged(); + } + +private: + Engine::Configuration config_; + Engine *engine_; +}; + +class EngineThread : public QThread +{ + Q_OBJECT + +public: + QMutex m; + QWaitCondition w; + Engine::Configuration config; + DomainMap *domainMap; + EngineWorker *worker; + + EngineThread(const Engine::Configuration &_config, DomainMap *_domainMap) : + config(_config), + domainMap(_domainMap), + worker(0) + { + } + + ~EngineThread() + { + stop(); + wait(); + } + + bool start() + { + setObjectName("proxy-worker-" + QString::number(config.id)); + + QMutexLocker locker(&m); + QThread::start(); + w.wait(&m); + return (bool)worker; + } + + void stop() + { + QMutexLocker locker(&m); + + if(worker) + QMetaObject::invokeMethod(worker, "stop", Qt::QueuedConnection); + } + + void routesChanged() + { + QMutexLocker locker(&m); + + if(worker) + QMetaObject::invokeMethod(worker, "routesChanged", Qt::QueuedConnection); + } + + virtual void run() + { + // will unlock during exec + m.lock(); + + worker = new EngineWorker(config, domainMap); + Connection startedConnection = worker->started.connect(boost::bind(&EngineThread::worker_started, this)); + Connection stoppedConnection = worker->stopped.connect(boost::bind(&EngineThread::worker_stopped, this)); + Connection errorConnection = worker->error.connect(boost::bind(&EngineThread::worker_error, this)); + QMetaObject::invokeMethod(worker, "start", Qt::QueuedConnection); + exec(); + + // ensure deferred deletes are processed + QCoreApplication::instance()->sendPostedEvents(); + + // deinit here, after all event loop activity has completed + RTimer::deinit(); + } + +private: + void worker_started() + { + log_debug("worker %d: started", config.id); + + // unblock start() + w.wakeOne(); + m.unlock(); + } + + void worker_stopped() + { + delete worker; + worker = 0; + + log_debug("worker %d: stopped", config.id); + + quit(); + } + + void worker_error() + { + delete worker; + worker = 0; + + quit(); + + // unblock start() + w.wakeOne(); + m.unlock(); + } +}; + class App::Private : public QObject { Q_OBJECT @@ -169,14 +341,16 @@ public: App *q; ArgsData args; - Engine *engine; + DomainMap *domainMap; + std::list threads; Connection quitConnection; Connection hupConnection; + Connection changedConnection; Private(App *_q) : QObject(_q), q(_q), - engine(0) + domainMap(0) { quitConnection = ProcessQuit::instance()->quit.connect(boost::bind(&Private::doQuit, this)); hupConnection = ProcessQuit::instance()->hup.connect(boost::bind(&App::Private::reload, this)); @@ -250,6 +424,7 @@ QStringList services = settings.value("runner/services").toStringList(); + int workerCount = settings.value("proxy/workers", 1).toInt(); QStringList condure_in_specs = settings.value("proxy/condure_in_specs").toStringList(); trimlist(&condure_in_specs); QStringList condure_in_stream_specs = settings.value("proxy/condure_in_stream_specs").toStringList(); @@ -277,8 +452,10 @@ QString handler_inspect_spec = settings.value("proxy/handler_inspect_spec").toString(); QString handler_accept_spec = settings.value("proxy/handler_accept_spec").toString(); QString handler_retry_in_spec = settings.value("proxy/handler_retry_in_spec").toString(); - QString handler_ws_control_in_spec = settings.value("proxy/handler_ws_control_in_spec").toString(); - QString handler_ws_control_out_spec = settings.value("proxy/handler_ws_control_out_spec").toString(); + QStringList handler_ws_control_init_specs = settings.value("proxy/handler_ws_control_init_specs").toStringList(); + trimlist(&handler_ws_control_init_specs); + QStringList handler_ws_control_stream_specs = settings.value("proxy/handler_ws_control_stream_specs").toStringList(); + trimlist(&handler_ws_control_stream_specs); QString stats_spec = settings.value("proxy/stats_spec").toString(); QString command_spec = settings.value("proxy/command_spec").toString(); QStringList intreq_in_specs = settings.value("proxy/intreq_in_specs").toStringList(); @@ -289,7 +466,7 @@ trimlist(&intreq_out_specs); bool ok; int ipcFileMode = settings.value("proxy/ipc_file_mode", -1).toString().toInt(&ok, 8); - int maxWorkers = settings.value("proxy/max_open_requests", -1).toInt(); + int sessionsMax = settings.value("proxy/max_open_requests", -1).toInt(); QString routesFile = settings.value("proxy/routesfile").toString(); bool debug = settings.value("proxy/debug").toBool(); bool autoCrossOrigin = settings.value("proxy/auto_cross_origin").toBool(); @@ -345,6 +522,23 @@ if(updatesCheck == "true") updatesCheck = "check"; + // sessionsMax should not exceed clientMaxconn + if(sessionsMax >= 0) + sessionsMax = qMin(sessionsMax, clientMaxconn); + else + sessionsMax = clientMaxconn; + + if(!args.routeLines.isEmpty()) + { + domainMap = new DomainMap(this); + foreach(const QString &line, args.routeLines) + domainMap->addRouteLine(line); + } + else + domainMap = new DomainMap(routesFile, this); + + changedConnection = domainMap->changed.connect(boost::bind(&Private::domainMap_changed, this)); + Engine::Configuration config; config.appVersion = Config::get().version; config.clientId = "pushpin-proxy_" + QByteArray::number(QCoreApplication::applicationPid()); @@ -375,19 +569,15 @@ config.inspectSpec = handler_inspect_spec; config.acceptSpec = handler_accept_spec; config.retryInSpec = handler_retry_in_spec; - config.wsControlInSpec = handler_ws_control_in_spec; - config.wsControlOutSpec = handler_ws_control_out_spec; + config.wsControlInitSpecs = handler_ws_control_init_specs; + config.wsControlStreamSpecs = handler_ws_control_stream_specs; config.statsSpec = stats_spec; config.commandSpec = command_spec; config.intServerInSpecs = intreq_in_specs; config.intServerInStreamSpecs = intreq_in_stream_specs; config.intServerOutSpecs = intreq_out_specs; config.ipcFileMode = ipcFileMode; - config.maxWorkers = maxWorkers; - if(!args.routeLines.isEmpty()) - config.routeLines = args.routeLines; - else - config.routesFile = routesFile; + config.sessionsMax = sessionsMax / workerCount; config.debug = debug; config.autoCrossOrigin = autoCrossOrigin; config.acceptXForwardedProto = acceptXForwardedProtocol; @@ -407,7 +597,6 @@ config.updatesCheck = updatesCheck; config.organizationName = organizationName; config.quietCheck = args.quietCheck; - config.connectionsMax = clientMaxconn; config.statsConnectionSend = statsConnectionSend; config.statsConnectionTtl = statsConnectionTtl; config.statsConnectionsMaxTtl = statsConnectionsMaxTtl; @@ -415,11 +604,43 @@ config.prometheusPort = prometheusPort; config.prometheusPrefix = prometheusPrefix; - engine = new Engine(this); - if(!engine->start(config)) + for(int n = 0; n < workerCount; ++n) { - q->quit(0); - return; + Engine::Configuration wconfig = config; + + wconfig.id = n; + + if(workerCount > 1) + { + wconfig.clientId += '-' + QByteArray::number(n); + + wconfig.inspectSpec = suffixSpec(wconfig.inspectSpec, n); + wconfig.acceptSpec = suffixSpec(wconfig.acceptSpec, n); + wconfig.retryInSpec = suffixSpec(wconfig.retryInSpec, n); + wconfig.wsControlInitSpecs = suffixSpecs(wconfig.wsControlInitSpecs, n); + wconfig.wsControlStreamSpecs = suffixSpecs(wconfig.wsControlStreamSpecs, n); + wconfig.statsSpec = suffixSpec(wconfig.statsSpec, n); + wconfig.commandSpec = suffixSpec(wconfig.commandSpec, n); + wconfig.intServerInSpecs = suffixSpecs(wconfig.intServerInSpecs, n); + wconfig.intServerInStreamSpecs = suffixSpecs(wconfig.intServerInStreamSpecs, n); + wconfig.intServerOutSpecs = suffixSpecs(wconfig.intServerOutSpecs, n); + } + + EngineThread *t = new EngineThread(wconfig, domainMap); + if(!t->start()) + { + delete t; + + for(EngineThread *t : threads) + delete t; + + threads.clear(); + + q->quit(0); + return; + } + + threads.push_back(t); } log_info("started"); @@ -430,7 +651,14 @@ { log_info("reloading"); log_rotate(); - engine->reload(); + + domainMap->reload(); + } + + void domainMap_changed() + { + for(EngineThread *t : threads) + t->routesChanged(); } void doQuit() @@ -440,8 +668,13 @@ // remove the handler, so if we get another signal then we crash out ProcessQuit::cleanup(); - delete engine; - engine = 0; + for(EngineThread *t : threads) + t->stop(); + + for(EngineThread *t : threads) + delete t; + + threads.clear(); log_info("stopped"); q->quit(0); diff -Nru pushpin-1.38.0/src/cpp/proxy/domainmap.cpp pushpin-1.39.1/src/cpp/proxy/domainmap.cpp --- pushpin-1.38.0/src/cpp/proxy/domainmap.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/domainmap.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -740,6 +740,7 @@ Connection startedConnection = worker->started.connect(boost::bind(&Thread::worker_started, this)); QMetaObject::invokeMethod(worker, "start", Qt::QueuedConnection); exec(); + startedConnection.disconnect(); delete worker; } @@ -769,6 +770,7 @@ ~Private() { + changedConnection.disconnect(); delete thread; } diff -Nru pushpin-1.38.0/src/cpp/proxy/engine.cpp pushpin-1.39.1/src/cpp/proxy/engine.cpp --- pushpin-1.38.0/src/cpp/proxy/engine.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/engine.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2012-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -26,11 +26,13 @@ #include #include "qzmqsocket.h" #include "qzmqvalve.h" +#include "qzmqreqmessage.h" #include "tnetstring.h" #include "packet/httpresponsedata.h" #include "packet/retryrequestpacket.h" #include "packet/statspacket.h" #include "packet/zrpcrequestpacket.h" +#include "qtcompat.h" #include "rtimer.h" #include "log.h" #include "inspectdata.h" @@ -87,15 +89,28 @@ } }; + struct RequestSessionConnections { + Connection inspectedConnection; + Connection inspectErrorConnection; + Connection finishedConnection; + Connection finishedByAcceptConnection; + }; + + struct ProxySessionConnections { + Connection addNotAllowedConnection; + Connection finishedConnection; + Connection reqSessionDestroyedConnection; + }; + Engine *q; bool destroying; + DomainMap *domainMap; Configuration config; ZhttpManager *zhttpIn; ZhttpManager *intZhttpIn; ZRoutes *zroutes; ZrpcManager *inspect; - WsControlManager *wsControl; - DomainMap *domainMap; + std::unique_ptr wsControl; ZrpcChecker *inspectChecker; StatsManager *stats; ZrpcManager *command; @@ -110,23 +125,25 @@ ConnectionManager connectionManager; Updater *updater; LogUtil::Config logConfig; - Connection changedConnection; Connection cmdReqReadyConnection; Connection sessionReadyConnection; Connection requestReadyConnection; Connection socketReadyConnection; Connection iRequestReadyConnection; - - Private(Engine *_q) : + map reqSessionConnectionMap; + map proxySessionConnectionMap; + Connection connMaxConnection; + Connection rrConnection; + + Private(Engine *_q, DomainMap *_domainMap) : QObject(_q), q(_q), destroying(false), + domainMap(_domainMap), zhttpIn(0), intZhttpIn(0), zroutes(0), inspect(0), - wsControl(0), - domainMap(0), inspectChecker(0), stats(0), command(0), @@ -142,7 +159,8 @@ { destroying = true; - // need to delete all objects that may have outgoing connections before zroutes + // need to delete all objects that may have connections before + // deleting zhttpmanagers/zroutes delete updater; @@ -167,10 +185,16 @@ wsProxyItemsBySession.clear(); - foreach(RequestSession *rs, requestSessions) + foreach(RequestSession *rs, requestSessions){ + reqSessionConnectionMap.erase(rs); delete rs; + } requestSessions.clear(); + // may have background connections + delete sockJsManager; + sockJsManager = 0; + WebSocketOverHttp::clearDisconnectManager(); // need to make sure this is deleted before inspect manager @@ -182,24 +206,13 @@ { config = _config; - // up to 10 timers per connection - RTimer::init(config.connectionsMax * 10); + // up to 10 timers per session + RTimer::init(config.sessionsMax * 10); logConfig.fromAddress = config.logFrom; logConfig.userAgent = config.logUserAgent; - WebSocketOverHttp::setMaxManagedDisconnects(config.maxWorkers); - - if(!config.routeLines.isEmpty()) - { - domainMap = new DomainMap(this); - foreach(const QString &line, config.routeLines) - domainMap->addRouteLine(line); - } - else - domainMap = new DomainMap(config.routesFile, this); - - changedConnection = domainMap->changed.connect(boost::bind(&Private::domainMap_changed, this)); + WebSocketOverHttp::setMaxManagedDisconnects(config.sessionsMax); zhttpIn = new ZhttpManager(this); requestReadyConnection = zhttpIn->requestReady.connect(boost::bind(&Private::zhttpIn_requestReady, this)); @@ -251,6 +264,7 @@ if(!config.acceptSpec.isEmpty()) { accept = new ZrpcManager(this); + accept->setInstanceId(config.clientId); accept->setBind(true); accept->setIpcFileMode(config.ipcFileMode); if(!accept->setClientSpecs(QStringList() << config.acceptSpec)) @@ -265,8 +279,9 @@ if(!config.retryInSpec.isEmpty()) { - handler_retry_in_sock = new QZmq::Socket(QZmq::Socket::Pull, this); + handler_retry_in_sock = new QZmq::Socket(QZmq::Socket::Router, this); + handler_retry_in_sock->setIdentity(config.clientId); handler_retry_in_sock->setHwm(DEFAULT_HWM); QString errorMessage; @@ -277,36 +292,37 @@ } handler_retry_in_valve = new QZmq::Valve(handler_retry_in_sock, this); - connect(handler_retry_in_valve, &QZmq::Valve::readyRead, this, &Private::handler_retry_in_readyRead); + rrConnection = handler_retry_in_valve->readyRead.connect(boost::bind(&Private::handler_retry_in_readyRead, this, boost::placeholders::_1)); } if(handler_retry_in_valve) handler_retry_in_valve->open(); - if(!config.wsControlInSpec.isEmpty() && !config.wsControlOutSpec.isEmpty()) + if(!config.wsControlInitSpecs.isEmpty() && !config.wsControlStreamSpecs.isEmpty()) { - wsControl = new WsControlManager(this); + wsControl = std::make_unique(); + wsControl->setIdentity(config.clientId); wsControl->setIpcFileMode(config.ipcFileMode); - if(!wsControl->setInSpec(config.wsControlInSpec)) + if(!wsControl->setInitSpecs(config.wsControlInitSpecs)) { - log_error("unable to bind to handler_ws_control_in_spec: %s", qPrintable(config.wsControlInSpec)); + log_error("unable to bind to handler_ws_control_init_specs: %s", qPrintable(config.wsControlInitSpecs.join(", "))); return false; } - if(!wsControl->setOutSpec(config.wsControlOutSpec)) + if(!wsControl->setStreamSpecs(config.wsControlStreamSpecs)) { - log_error("unable to bind to handler_ws_control_out_spec: %s", qPrintable(config.wsControlOutSpec)); + log_error("unable to bind to handler_ws_control_stream_specs: %s", qPrintable(config.wsControlStreamSpecs.join(", "))); return false; } } if(!config.statsSpec.isEmpty() || !config.prometheusPort.isEmpty()) { - stats = new StatsManager(config.connectionsMax, 0, this); + stats = new StatsManager(config.sessionsMax, 0, this); - connect(stats, &StatsManager::connMax, this, &Private::stats_connMax); + connMaxConnection = stats->connMax.connect(boost::bind(&Private::stats_connMax, this, boost::placeholders::_1)); stats->setInstanceId(config.clientId); stats->setIpcFileMode(config.ipcFileMode); @@ -357,14 +373,15 @@ } // init zroutes - domainMap_changed(); + routesChanged(); return true; } - void reload() + void routesChanged() { - domainMap->reload(); + // connect to new zhttp targets, disconnect from old + zroutes->setup(domainMap->zhttpRoutes()); } void doProxy(RequestSession *rs, const InspectData *idata = 0) @@ -392,9 +409,11 @@ ps = new ProxySession(zroutes, accept, logConfig, stats); // TODO: use callbacks for performance - connect(ps, &ProxySession::addNotAllowed, this, &Private::ps_addNotAllowed); - connect(ps, &ProxySession::finished, this, &Private::ps_finished); - connect(ps, &ProxySession::requestSessionDestroyed, this, &Private::ps_requestSessionDestroyed); + proxySessionConnectionMap[ps] = { + ps->addNotAllowed.connect(boost::bind(&Private::ps_addNotAllowed, this, ps)), + ps->finished.connect(boost::bind(&Private::ps_finished, this, ps)), + ps->requestSessionDestroyed.connect(boost::bind(&Private::ps_requestSessionDestroyed, this, boost::placeholders::_1, boost::placeholders::_2)) + }; ps->setRoute(route); ps->setDefaultSigKey(config.sigIss, config.sigKey); @@ -425,7 +444,7 @@ // proxysession will take it from here // TODO: use callbacks for performance - rs->disconnect(this); + reqSessionConnectionMap.erase(rs); ps->add(rs); } @@ -434,7 +453,7 @@ { QByteArray cid = connectionManager.addConnection(sock); - WsProxySession *ps = new WsProxySession(zroutes, &connectionManager, logConfig, stats, wsControl); + WsProxySession *ps = new WsProxySession(zroutes, &connectionManager, logConfig, stats, wsControl.get()); ps->finishedByPassthroughCallback().add(Private::wsps_finishedByPassthrough_cb, this); connectionManager.setProxyForConnection(sock, ps); @@ -466,13 +485,13 @@ bool canTake() { - // don't accept new connections during shutdown + // don't accept new sessions during shutdown if(destroying) return false; - // don't accept new connections if we're servicing maximum - int curWorkers = requestSessions.count() + wsProxyItemsBySession.count(); - if(config.maxWorkers != -1 && curWorkers >= config.maxWorkers) + // don't accept new sessions if we're servicing maximum + int curSessions = requestSessions.count() + wsProxyItemsBySession.count(); + if(curSessions >= config.sessionsMax) return false; return true; @@ -534,7 +553,8 @@ routeId = QString::fromUtf8(req->requestHeaders().get("Pushpin-Route")); } - RequestSession *rs; + RequestSession *rs = new RequestSession(config.id, domainMap, sockJsManager, inspect, inspectChecker, accept, stats); + if(passthroughData.isValid() && !preferInternal) { // passthrough request with preferInternal=false. in this case, @@ -566,7 +586,6 @@ route.targets += target; - rs = new RequestSession(stats); rs->setRoute(route); } else @@ -575,22 +594,29 @@ // request with preferInternal=true. in that case, use domainmap // for lookup, with route ID if available - rs = new RequestSession(domainMap, sockJsManager, inspect, inspectChecker, accept, stats); + rs->setRouteId(routeId); + } + + if(!passthroughData.isValid()) + { + // these only make sense on regular requests + rs->setDebugEnabled(config.debug); rs->setAutoCrossOrigin(config.autoCrossOrigin); rs->setPrefetchSize(config.inspectPrefetch); rs->setDefaultUpstreamKey(config.upstreamKey); rs->setXffRules(config.xffUntrustedRule, config.xffTrustedRule); - rs->setRouteId(routeId); } rs->setAutoShare(autoShare); // TODO: use callbacks for performance - connect(rs, &RequestSession::inspected, this, &Private::rs_inspected); - connect(rs, &RequestSession::inspectError, this, &Private::rs_inspectError); - connect(rs, &RequestSession::finished, this, &Private::rs_finished); - connect(rs, &RequestSession::finishedByAccept, this, &Private::rs_finishedByAccept); + reqSessionConnectionMap[rs] = { + rs->inspected.connect(boost::bind(&Private::rs_inspected, this, boost::placeholders::_1, rs)), + rs->inspectError.connect(boost::bind(&Private::rs_inspectError, this, rs)), + rs->finished.connect(boost::bind(&Private::rs_finished, this, rs)), + rs->finishedByAccept.connect(boost::bind(&Private::rs_finishedByAccept, this, rs)) + }; requestSessions += rs; @@ -611,7 +637,7 @@ QUrl requestUri = sock->requestUri(); - log_debug("IN ws id=%s, %s", sock->rid().second.data(), requestUri.toEncoded().data()); + log_debug("worker %d: IN ws id=%s, %s", config.id, sock->rid().second.data(), requestUri.toEncoded().data()); bool isSecure = (requestUri.scheme() == "wss"); QString host = requestUri.host(); @@ -707,20 +733,25 @@ { tryTakeNext(); } + void intZhttpIn_requestReady() { tryTakeNext(); } + void sockjs_sessionReady() { tryTakeNext(); } -private slots: - void rs_inspected(const InspectData &idata) + void rs_inspectError(RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); + // default action is to proxy without sharing + doProxy(rs); + } + void rs_inspected(const InspectData &idata, RequestSession *rs) + { // if we get here, then the request must be proxied. if it was to be directly // accepted, then finishedByAccept would have been emitted instead assert(idata.doProxy); @@ -728,43 +759,31 @@ doProxy(rs, &idata); } - void rs_inspectError() - { - RequestSession *rs = (RequestSession *)sender(); - - // default action is to proxy without sharing - doProxy(rs); - } - - void rs_finished() + void rs_finished(RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); - if(!rs->isSockJs()) logFinished(rs); requestSessions.remove(rs); + reqSessionConnectionMap.erase(rs); delete rs; tryTakeNext(); } - void rs_finishedByAccept() + void rs_finishedByAccept(RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); - logFinished(rs, true); requestSessions.remove(rs); + reqSessionConnectionMap.erase(rs); delete rs; tryTakeNext(); } - void ps_addNotAllowed() + void ps_addNotAllowed(ProxySession *ps) { - ProxySession *ps = (ProxySession *)sender(); - ProxyItem *i = proxyItemsBySession.value(ps); assert(i); @@ -776,13 +795,13 @@ } } - void ps_finished() + void ps_finished(ProxySession *ps) { - ProxySession *ps = (ProxySession *)sender(); - ProxyItem *i = proxyItemsBySession.value(ps); assert(i); + proxySessionConnectionMap.erase(ps); + if(i->shared) proxyItemsByKey.remove(i->key); proxyItemsBySession.remove(i->ps); @@ -827,16 +846,19 @@ tryTakeNext(); } +private: void handler_retry_in_readyRead(const QList &message) { - if(message.count() != 1) + QZmq::ReqMessage req(message); + + if(req.content().count() != 1) { log_warning("retry: received message with parts != 1, skipping"); return; } bool ok; - QVariant data = TnetString::toVariant(message[0], 0, &ok); + QVariant data = TnetString::toVariant(req.content()[0], 0, &ok); if(!ok) { log_warning("retry: received message with invalid format (tnetstring parse failed), skipping"); @@ -855,9 +877,6 @@ log_debug("IN (retry) %s %s", qPrintable(p.requestData.method), p.requestData.uri.toEncoded().data()); - if(p.retrySeq >= 0) - stats->setRetrySeq(p.route, p.retrySeq); - InspectData idata; if(p.haveInspectInfo) { @@ -886,7 +905,7 @@ ZhttpRequest *zhttpRequest = zhttpIn->createRequestFromState(ss); - RequestSession *rs = new RequestSession(domainMap, sockJsManager, inspect, inspectChecker, accept, stats); + RequestSession *rs = new RequestSession(config.id, domainMap, sockJsManager, inspect, inspectChecker, accept, stats); requestSessions += rs; @@ -899,7 +918,7 @@ // note: if the routing table was changed, there's a chance the request // might get a different route id this time around. this could confuse // stats processors tracking route+connection mappings. - rs->startRetry(zhttpRequest, req.debug, req.autoCrossOrigin, req.jsonpCallback, req.jsonpExtendedResponse, req.unreportedTime); + rs->startRetry(zhttpRequest, req.debug, req.autoCrossOrigin, req.jsonpCallback, req.jsonpExtendedResponse, req.unreportedTime, p.retrySeq); doProxy(rs, p.haveInspectInfo ? &idata : 0); } @@ -917,7 +936,6 @@ } } -private: void command_requestReady() { ZrpcRequest *req = command->takeNext(); @@ -931,7 +949,7 @@ } QVariantHash args = req->args(); - if(!args.contains("ids") || args["ids"].type() != QVariant::List) + if(!args.contains("ids") || typeId(args["ids"]) != QMetaType::QVariantList) { req->respondError("bad-format"); delete req; @@ -944,7 +962,7 @@ QList ids; foreach(const QVariant &vid, vids) { - if(vid.type() != QVariant::ByteArray) + if(typeId(vid) != QMetaType::QByteArray) { ok = false; break; @@ -971,7 +989,7 @@ else if(req->method() == "refresh") { QVariantHash args = req->args(); - if(!args.contains("cid") || args["cid"].type() != QVariant::ByteArray) + if(!args.contains("cid") || typeId(args["cid"]) != QMetaType::QByteArray) { req->respondError("bad-format"); delete req; @@ -997,7 +1015,7 @@ else if(req->method() == "report") { QVariantHash args = req->args(); - if(!args.contains("stats") || args["stats"].type() != QVariant::Hash) + if(!args.contains("stats") || typeId(args["stats"]) != QMetaType::QVariantHash) { req->respondError("bad-format"); delete req; @@ -1047,18 +1065,12 @@ delete req; } - - void domainMap_changed() - { - // connect to new zhttp targets, disconnect from old - zroutes->setup(domainMap->zhttpRoutes()); - } }; -Engine::Engine(QObject *parent) : +Engine::Engine(DomainMap *domainMap, QObject *parent) : QObject(parent) { - d = new Private(this); + d = new Private(this, domainMap); } Engine::~Engine() @@ -1076,9 +1088,9 @@ return d->start(config); } -void Engine::reload() +void Engine::routesChanged() { - d->reload(); + d->routesChanged(); } #include "engine.moc" diff -Nru pushpin-1.38.0/src/cpp/proxy/engine.h pushpin-1.39.1/src/cpp/proxy/engine.h --- pushpin-1.38.0/src/cpp/proxy/engine.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/engine.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2012-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -30,10 +30,13 @@ #include "jwt.h" #include "xffrule.h" #include +#include +using std::map; using Connection = boost::signals2::scoped_connection; class StatsManager; +class DomainMap; class Engine : public QObject { @@ -43,6 +46,7 @@ class Configuration { public: + int id; QString appVersion; QByteArray clientId; QStringList serverInSpecs; @@ -54,19 +58,17 @@ QString inspectSpec; QString acceptSpec; QString retryInSpec; - QString wsControlInSpec; - QString wsControlOutSpec; + QStringList wsControlInitSpecs; + QStringList wsControlStreamSpecs; QString statsSpec; QString commandSpec; QStringList intServerInSpecs; QStringList intServerInStreamSpecs; QStringList intServerOutSpecs; int ipcFileMode; - int maxWorkers; + int sessionsMax; int inspectTimeout; int inspectPrefetch; - QString routesFile; - QStringList routeLines; bool debug; bool autoCrossOrigin; bool acceptXForwardedProto; @@ -86,7 +88,6 @@ QString updatesCheck; QString organizationName; bool quietCheck; - int connectionsMax; bool statsConnectionSend; int statsConnectionTtl; int statsConnectionsMaxTtl; @@ -95,8 +96,9 @@ QString prometheusPrefix; Configuration() : + id(0), ipcFileMode(-1), - maxWorkers(-1), + sessionsMax(-1), inspectTimeout(8000), inspectPrefetch(10000), debug(false), @@ -109,7 +111,6 @@ logUserAgent(false), updatesCheck("check"), quietCheck(false), - connectionsMax(-1), statsConnectionSend(false), statsConnectionTtl(-1), statsConnectionsMaxTtl(-1), @@ -118,13 +119,13 @@ } }; - Engine(QObject *parent = 0); + Engine(DomainMap *domainMap, QObject *parent = 0); ~Engine(); StatsManager *statsManager() const; bool start(const Configuration &config); - void reload(); + void routesChanged(); private: class Private; diff -Nru pushpin-1.38.0/src/cpp/proxy/inspectrequest.cpp pushpin-1.39.1/src/cpp/proxy/inspectrequest.cpp --- pushpin-1.38.0/src/cpp/proxy/inspectrequest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/inspectrequest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2015 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,13 +24,14 @@ #include "inspectrequest.h" #include "packet/httprequestdata.h" +#include "qtcompat.h" #include "inspectdata.h" static InspectData resultToData(const QVariant &in, bool *ok) { InspectData out; - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) { *ok = false; return InspectData(); @@ -37,7 +39,7 @@ QVariantHash obj = in.toHash(); - if(!obj.contains("no-proxy") || obj["no-proxy"].type() != QVariant::Bool) + if(!obj.contains("no-proxy") || typeId(obj["no-proxy"]) != QMetaType::Bool) { *ok = false; return InspectData(); @@ -47,7 +49,7 @@ out.sharingKey.clear(); if(obj.contains("sharing-key")) { - if(obj["sharing-key"].type() != QVariant::ByteArray) + if(typeId(obj["sharing-key"]) != QMetaType::QByteArray) { *ok = false; return InspectData(); @@ -59,7 +61,7 @@ out.sid.clear(); if(obj.contains("sid")) { - if(obj["sid"].type() != QVariant::ByteArray) + if(typeId(obj["sid"]) != QMetaType::QByteArray) { *ok = false; return InspectData(); @@ -71,7 +73,7 @@ out.lastIds.clear(); if(obj.contains("last-ids")) { - if(obj["last-ids"].type() != QVariant::Hash) + if(typeId(obj["last-ids"]) != QMetaType::QVariantHash) { *ok = false; return InspectData(); @@ -83,7 +85,7 @@ { it.next(); - if(it.value().type() != QVariant::ByteArray) + if(typeId(it.value()) != QMetaType::QByteArray) { *ok = false; return InspectData(); diff -Nru pushpin-1.38.0/src/cpp/proxy/proxysession.cpp pushpin-1.39.1/src/cpp/proxy/proxysession.cpp --- pushpin-1.38.0/src/cpp/proxy/proxysession.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/proxysession.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2012-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -31,6 +31,7 @@ #include "packet/statspacket.h" #include "packet/httprequestdata.h" #include "packet/httpresponsedata.h" +#include "qtcompat.h" #include "bufferlist.h" #include "log.h" #include "jwt.h" @@ -47,6 +48,8 @@ #include "acceptrequest.h" #include "testhttprequest.h" +using std::map; + #define MAX_ACCEPT_REQUEST_BODY 100000 // NOTE: if this value is ever changed, fix enginetest to match @@ -102,6 +105,21 @@ } }; + struct RequestSessionConnections { + Connection bytesWrittenConnection; + Connection errorRespondingConnection; + Connection pausedConnection; + Connection finishedConnection; + Connection headerBytesSentConnection; + Connection bodyBytesSentConnection; + }; + + struct ZhttpReqConnections { + Connection readyReadConnection; + Connection writeBytesChangedConnection; + Connection errorConnection; + }; + ProxySession *q; State state; ZRoutes *zroutes; @@ -151,10 +169,9 @@ StatsManager *statsManager; Connection inReqReadyReadConnection; Connection inReqErrorConnection; - Connection readyReadConnection; - Connection writeBytesChangedConnection; - Connection errorConnection; + ZhttpReqConnections zhttpReqConnections; Connection finishedConnection; + map reqSessionConnectionMap; Private(ProxySession *_q, ZRoutes *_zroutes, ZrpcManager *_acceptManager, const LogUtil::Config &_logConfig, StatsManager *_statsManager) : QObject(_q), @@ -198,7 +215,7 @@ foreach(SessionItem *si, sessionItems) { // emitting a signal here is gross, but this way the engine cleans up the request sessions - emit q->requestSessionDestroyed(si->rs, false); + q->requestSessionDestroyed(si->rs, false); delete si->rs; delete si; } @@ -238,12 +255,14 @@ sessionItems += si; sessionItemsBySession.insert(rs, si); - connect(rs, &RequestSession::bytesWritten, this, &Private::rs_bytesWritten); - connect(rs, &RequestSession::errorResponding, this, &Private::rs_errorResponding); - connect(rs, &RequestSession::finished, this, &Private::rs_finished); - connect(rs, &RequestSession::paused, this, &Private::rs_paused); - connect(rs, &RequestSession::headerBytesSent, this, &Private::rs_headerBytesSent); - connect(rs, &RequestSession::bodyBytesSent, this, &Private::rs_bodyBytesSent); + reqSessionConnectionMap[rs] = { + rs->bytesWritten.connect(boost::bind(&Private::rs_bytesWritten, this, boost::placeholders::_1, rs)), + rs->errorResponding.connect(boost::bind(&Private::rs_errorResponding, this, rs)), + rs->paused.connect(boost::bind(&Private::rs_paused, this, rs)), + rs->finished.connect(boost::bind(&Private::rs_finished, this, rs)), + rs->headerBytesSent.connect(boost::bind(&Private::rs_headerBytesSent, this, boost::placeholders::_1, rs)), + rs->bodyBytesSent.connect(boost::bind(&Private::rs_bodyBytesSent, this, boost::placeholders::_1, rs)) + }; HttpRequestData rsRequestData = rs->requestData(); @@ -442,9 +461,11 @@ zhttpRequest->setParent(this); } - readyReadConnection = zhttpRequest->readyRead.connect(boost::bind(&Private::zhttpRequest_readyRead, this)); - writeBytesChangedConnection = zhttpRequest->writeBytesChanged.connect(boost::bind(&Private::zhttpRequest_writeBytesChanged, this)); - errorConnection = zhttpRequest->error.connect(boost::bind(&Private::zhttpRequest_error, this)); + zhttpReqConnections = { + zhttpRequest->readyRead.connect(boost::bind(&Private::zhttpRequest_readyRead, this)), + zhttpRequest->writeBytesChanged.connect(boost::bind(&Private::zhttpRequest_writeBytesChanged, this)), + zhttpRequest->error.connect(boost::bind(&Private::zhttpRequest_error, this)) + }; if(target.trusted) zhttpRequest->setIgnorePolicies(true); @@ -481,7 +502,8 @@ // no need to track the primary request anymore if(inRequest) { - inRequest->request()->disconnect(this); + inReqReadyReadConnection.disconnect(); + inReqErrorConnection.disconnect(); inRequest = 0; } @@ -533,7 +555,8 @@ if(!requestBodySent && inRequest->request()->isInputFinished() && inRequest->request()->bytesAvailable() == 0) { // no need to track the primary request anymore - inRequest->request()->disconnect(this); + inReqReadyReadConnection.disconnect(); + inReqErrorConnection.disconnect(); inRequest = 0; requestBodySent = true; @@ -603,6 +626,7 @@ void rejectAll(int code, const QString &reason, const QString &errorMessage, const QString &debugErrorMessage) { + zhttpReqConnections = ZhttpReqConnections(); // kill the active target request, if any delete zhttpRequest; zhttpRequest = 0; @@ -890,7 +914,7 @@ if(wasAllowed && !addAllowed) { - emit q->addNotAllowed(); + q->addNotAllowed(); if(!self) return; } @@ -914,6 +938,7 @@ return; } + zhttpReqConnections = ZhttpReqConnections(); delete zhttpRequest; zhttpRequest = 0; @@ -921,7 +946,7 @@ if(addAllowed) { addAllowed = false; - emit q->addNotAllowed(); + q->addNotAllowed(); if(!self) return; } @@ -1167,11 +1192,8 @@ } } -public slots: - void rs_bytesWritten(int count) + void rs_bytesWritten(int count, RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); - log_debug("proxysession: %p response bytes written id=%s: %d", q, rs->rid().second.data(), count); SessionItem *si = sessionItemsBySession.value(rs); @@ -1187,10 +1209,8 @@ tryResponseRead(); } - void rs_finished() + void rs_finished(RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); - log_debug("proxysession: %p response finished id=%s", q, rs->rid().second.data()); SessionItem *si = sessionItemsBySession.value(rs); @@ -1200,7 +1220,7 @@ logFinished(si); QPointer self = this; - emit q->requestSessionDestroyed(si->rs, false); + q->requestSessionDestroyed(si->rs, false); if(!self) return; @@ -1209,6 +1229,7 @@ sessionItemsBySession.remove(rs); sessionItems.remove(si); + reqSessionConnectionMap.erase(rs); delete rs; delete si; @@ -1216,7 +1237,7 @@ if(sessionItems.isEmpty()) { log_debug("proxysession: %p finished by passthrough", q); - emit q->finished(); + q->finished(); } else if(wasInputRequest) { @@ -1228,10 +1249,8 @@ } } - void rs_paused() + void rs_paused(RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); - log_debug("proxysession: %p response paused id=%s", q, rs->rid().second.data()); SessionItem *si = sessionItemsBySession.value(rs); @@ -1321,10 +1340,10 @@ if(!statsManager->connectionSendEnabled()) { // flush max. the count will include the connections we just unregistered - adata.connMaxPackets += statsManager->getConnMaxPacket(route.id).toVariant(); + adata.connMaxPackets += statsManager->getConnMaxPacket(route.statsRoute()).toVariant(); // flush max again to get the count without the connections - adata.connMaxPackets += statsManager->getConnMaxPacket(route.id).toVariant(); + adata.connMaxPackets += statsManager->getConnMaxPacket(route.statsRoute()).toVariant(); } acceptRequest = new AcceptRequest(acceptManager, this); @@ -1333,10 +1352,8 @@ } } - void rs_errorResponding() + void rs_errorResponding(RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); - log_debug("proxysession: %p response error id=%s", q, rs->rid().second.data()); SessionItem *si = sessionItemsBySession.value(rs); @@ -1351,10 +1368,8 @@ // don't destroy the RequestSession here. a finished signal will arrive next. } - void rs_headerBytesSent(int count) + void rs_headerBytesSent(int count, RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); - SessionItem *si = sessionItemsBySession.value(rs); assert(si); @@ -1362,10 +1377,8 @@ incCounter(Stats::ClientHeaderBytesSent, count); } - void rs_bodyBytesSent(int count) + void rs_bodyBytesSent(int count, RequestSession *rs) { - RequestSession *rs = (RequestSession *)sender(); - SessionItem *si = sessionItemsBySession.value(rs); assert(si); @@ -1373,13 +1386,13 @@ incCounter(Stats::ClientContentBytesSent, count); } -public: void acceptRequest_finished() { if(acceptRequest->success()) { AcceptRequest::ResponseData rdata = acceptRequest->result(); + finishedConnection.disconnect(); delete acceptRequest; acceptRequest = 0; @@ -1403,7 +1416,8 @@ QPointer self = this; foreach(RequestSession *rs, toDestroy) { - emit q->requestSessionDestroyed(rs, true); + q->requestSessionDestroyed(rs, true); + reqSessionConnectionMap.erase(rs); delete rs; if(!self) return; @@ -1411,7 +1425,7 @@ log_debug("proxysession: %p finished for accept", q); cleanup(); - emit q->finished(); + q->finished(); } else { @@ -1454,7 +1468,7 @@ { // wake up receivers and reject - if(acceptRequest->errorCondition() == ZrpcRequest::ErrorFormat && ((ZrpcRequest *)acceptRequest)->result().type() == QVariant::ByteArray) + if(acceptRequest->errorCondition() == ZrpcRequest::ErrorFormat && typeId(((ZrpcRequest *)acceptRequest)->result()) == QMetaType::QByteArray) { QString errorString = QString::fromUtf8(((ZrpcRequest *)acceptRequest)->result().toByteArray()); QString msg = "Error while proxying to origin."; @@ -1467,6 +1481,7 @@ cannotAcceptAll(); } + finishedConnection.disconnect(); delete acceptRequest; acceptRequest = 0; } diff -Nru pushpin-1.38.0/src/cpp/proxy/proxysession.h pushpin-1.39.1/src/cpp/proxy/proxysession.h --- pushpin-1.38.0/src/cpp/proxy/proxysession.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/proxysession.h 2024-03-18 18:42:24.000000000 +0000 @@ -40,6 +40,7 @@ class RequestSession; #include +using Signal = boost::signals2::signal; using Connection = boost::signals2::scoped_connection; class ProxySession : public QObject @@ -65,10 +66,9 @@ // takes ownership void add(RequestSession *rs); -signals: - void addNotAllowed(); // no more sharing, for whatever reason - void finished(); - void requestSessionDestroyed(RequestSession *rs, bool accept); + Signal addNotAllowed; // no more sharing, for whatever reason + Signal finished; + boost::signals2::signal requestSessionDestroyed; private: class Private; diff -Nru pushpin-1.38.0/src/cpp/proxy/proxyutil.cpp pushpin-1.39.1/src/cpp/proxy/proxyutil.cpp --- pushpin-1.38.0/src/cpp/proxy/proxyutil.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/proxyutil.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -25,6 +26,7 @@ #include #include #include +#include "qtcompat.h" #include "log.h" #include "jwt.h" #include "inspectdata.h" @@ -40,7 +42,7 @@ static bool validate_token(const QByteArray &token, const Jwt::DecodingKey &key) { QVariant claimObj = Jwt::decode(token, key); - if(!claimObj.isValid() || claimObj.type() != QVariant::Map) + if(!claimObj.isValid() || typeId(claimObj) != QMetaType::QVariantMap) return false; QVariantMap claim = claimObj.toMap(); diff -Nru pushpin-1.38.0/src/cpp/proxy/requestsession.cpp pushpin-1.39.1/src/cpp/proxy/requestsession.cpp --- pushpin-1.38.0/src/cpp/proxy/requestsession.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/requestsession.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2023 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -32,6 +33,7 @@ #include #include "packet/httprequestdata.h" #include "packet/httpresponsedata.h" +#include "qtcompat.h" #include "bufferlist.h" #include "log.h" #include "layertracker.h" @@ -145,7 +147,15 @@ RespondingInternal }; + struct ZhttpReqConnections { + Connection readyReadConnection; + Connection pausedConnection; + Connection errorConnection; + Connection bytesWrittenConnection; + }; + RequestSession *q; + int workerId; State state; ZhttpRequest::Rid rid; DomainMap *domainMap; @@ -187,16 +197,14 @@ XffRule xffRule; XffRule xffTrustedRule; bool isSockJs; - Connection errorConnection; - Connection pausedConnection; - Connection readyReadConnection; - Connection bytesWrittenConnection; + ZhttpReqConnections zhttpReqConnections; Connection inspectFinishedConnection; Connection acceptFinishedConnection; - Private(RequestSession *_q, DomainMap *_domainMap = 0, SockJsManager *_sockJsManager = 0, ZrpcManager *_inspectManager = 0, ZrpcChecker *_inspectChecker = 0, ZrpcManager *_acceptManager = 0, StatsManager *_stats = 0) : + Private(RequestSession *_q, int _workerId, DomainMap *_domainMap = 0, SockJsManager *_sockJsManager = 0, ZrpcManager *_inspectManager = 0, ZrpcChecker *_inspectChecker = 0, ZrpcManager *_acceptManager = 0, StatsManager *_stats = 0) : QObject(_q), q(_q), + workerId(_workerId), state(Stopped), domainMap(_domainMap), sockJsManager(_sockJsManager), @@ -235,13 +243,14 @@ { if(zhttpRequest) { + zhttpReqConnections = ZhttpReqConnections(); delete zhttpRequest; zhttpRequest = 0; } if(inspectRequest) { - inspectRequest->disconnect(this); + inspectFinishedConnection.disconnect(); inspectChecker->give(inspectRequest); inspectRequest = 0; } @@ -279,7 +288,7 @@ peerAddress = req->peerAddress(); logicalPeerAddress = ProxyUtil::getLogicalAddress(requestData.headers, trusted ? xffTrustedRule : xffRule, peerAddress); - log_debug("IN id=%s, %s %s", rid.second.data(), qPrintable(requestData.method), requestData.uri.toEncoded().data()); + log_debug("worker %d: IN id=%s, %s %s", workerId, rid.second.data(), qPrintable(requestData.method), requestData.uri.toEncoded().data()); bool isHttps = (requestData.uri.scheme() == "https"); QString host = requestData.uri.host(); @@ -300,13 +309,13 @@ isSockJs = true; sockJsManager->giveRequest(zhttpRequest, route.sockJsPath.length(), route.sockJsAsPath, route); zhttpRequest = 0; - QMetaObject::invokeMethod(q, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "doFinished", Qt::QueuedConnection); return; } } - pausedConnection = zhttpRequest->paused.connect(boost::bind(&Private::zhttpRequest_paused, this)); - errorConnection = zhttpRequest->error.connect(boost::bind(&Private::zhttpRequest_error, this)); + zhttpReqConnections.pausedConnection = zhttpRequest->paused.connect(boost::bind(&Private::zhttpRequest_paused, this)); + zhttpReqConnections.errorConnection = zhttpRequest->error.connect(boost::bind(&Private::zhttpRequest_error, this)); if(!route.isNull()) { @@ -379,19 +388,19 @@ state = Prefetching; - readyReadConnection = zhttpRequest->readyRead.connect(boost::bind(&Private::zhttpRequest_readyRead, this)); + zhttpReqConnections.readyReadConnection = zhttpRequest->readyRead.connect(boost::bind(&Private::zhttpRequest_readyRead, this)); processIncomingRequest(); } - void startRetry(int unreportedTime) + void startRetry(int unreportedTime, int retrySeq) { trusted = ProxyUtil::checkTrustedClient("requestsession", q, requestData, defaultUpstreamKey); peerAddress = zhttpRequest->peerAddress(); logicalPeerAddress = ProxyUtil::getLogicalAddress(requestData.headers, trusted ? xffTrustedRule : xffRule, peerAddress); - pausedConnection = zhttpRequest->paused.connect(boost::bind(&Private::zhttpRequest_paused, this)); - errorConnection = zhttpRequest->error.connect(boost::bind(&Private::zhttpRequest_error, this)); + zhttpReqConnections.pausedConnection = zhttpRequest->paused.connect(boost::bind(&Private::zhttpRequest_paused, this)); + zhttpReqConnections.errorConnection = zhttpRequest->error.connect(boost::bind(&Private::zhttpRequest_error, this)); state = WaitingForResponse; @@ -417,6 +426,9 @@ if(stats) { + if(retrySeq >= 0) + stats->setRetrySeq(route.statsRoute(), retrySeq); + connectionRegistered = true; int reportOffset = stats->connectionSendEnabled() ? -1 : qMax(unreportedTime, 0); @@ -439,7 +451,7 @@ { // we've read enough body to start inspection - readyReadConnection.disconnect(); + zhttpReqConnections.readyReadConnection.disconnect(); state = Inspecting; requestData.body = in.toByteArray(); @@ -484,11 +496,11 @@ // disallow sharing before passing to proxysession. at that // point, proxysession will read the remainder of the data - readyReadConnection.disconnect(); + zhttpReqConnections.readyReadConnection.disconnect(); state = WaitingForResponse; requestData.body = in.take(); - emit q->inspected(idata); + q->inspected(idata); } } else if(state == ReceivingForAccept) @@ -686,7 +698,7 @@ { vit.next(); - if(vit.value().type() != QVariant::String) + if(typeId(vit.value()) != QMetaType::QString) { log_debug("requestsession: id=%s invalid _headers parameter, rejecting", rid.second.data()); *ok = false; @@ -817,7 +829,7 @@ if(zhttpRequest->isFinished()) { cleanup(); - emit q->finished(); + q->finished(); } } @@ -867,14 +879,14 @@ { log_debug("requestsession: request error id=%s", rid.second.data()); cleanup(); - emit q->finished(); + q->finished(); } void inspectRequest_finished() { if(!inspectRequest->success()) { - inspectRequest->disconnect(this); + inspectFinishedConnection.disconnect(); inspectChecker->give(inspectRequest); inspectRequest = 0; @@ -884,7 +896,7 @@ idata = inspectRequest->result(); - inspectRequest->disconnect(this); + inspectFinishedConnection.disconnect(); inspectChecker->give(inspectRequest); inspectRequest = 0; @@ -894,7 +906,7 @@ // successful inspect indicated we should not proxy. in that case, // collect the body and accept - readyReadConnection = zhttpRequest->readyRead.connect(boost::bind(&Private::zhttpRequest_readyRead, this)); + zhttpReqConnections.readyReadConnection = zhttpRequest->readyRead.connect(boost::bind(&Private::zhttpRequest_readyRead, this)); processIncomingRequest(); } else @@ -905,14 +917,14 @@ // request body, so let's try to read it now state = Receiving; - readyReadConnection = zhttpRequest->readyRead.connect(boost::bind(&Private::zhttpRequest_readyRead, this)); + zhttpReqConnections.readyReadConnection = zhttpRequest->readyRead.connect(boost::bind(&Private::zhttpRequest_readyRead, this)); processIncomingRequest(); } else { state = WaitingForResponse; requestData.body = in.take(); - emit q->inspected(idata); + q->inspected(idata); } } } @@ -923,6 +935,7 @@ { AcceptRequest::ResponseData rdata = acceptRequest->result(); + acceptFinishedConnection.disconnect(); delete acceptRequest; acceptRequest = 0; @@ -931,11 +944,12 @@ accepted = true; // the request was paused, so deleting it will leave the peer session active + zhttpReqConnections = ZhttpReqConnections(); delete zhttpRequest; zhttpRequest = 0; cleanup(); - emit q->finishedByAccept(); + q->finishedByAccept(); } else { @@ -953,6 +967,7 @@ } else { + acceptFinishedConnection.disconnect(); delete acceptRequest; acceptRequest = 0; @@ -1004,7 +1019,7 @@ zhttpRequest->writeBody(body); responseBodySize += body.size(); zhttpRequest->endBody(); - emit q->errorResponding(); + q->errorResponding(); return; } @@ -1026,7 +1041,7 @@ } } - bytesWrittenConnection = zhttpRequest->bytesWritten.connect(boost::bind(&Private::zhttpRequest_bytesWritten, this, boost::placeholders::_1)); + zhttpReqConnections.bytesWrittenConnection = zhttpRequest->bytesWritten.connect(boost::bind(&Private::zhttpRequest_bytesWritten, this, boost::placeholders::_1)); zhttpRequest->beginResponse(200, "OK", headers); @@ -1051,14 +1066,14 @@ zhttpRequest->writeBody(body); responseBodySize += body.size(); zhttpRequest->endBody(); - emit q->errorResponding(); + q->errorResponding(); return; } headers += HttpHeader("Content-Type", "application/javascript"); headers += HttpHeader("Transfer-Encoding", "chunked"); - bytesWrittenConnection = zhttpRequest->bytesWritten.connect(boost::bind(&Private::zhttpRequest_bytesWritten, this, boost::placeholders::_1)); + zhttpReqConnections.bytesWrittenConnection = zhttpRequest->bytesWritten.connect(boost::bind(&Private::zhttpRequest_bytesWritten, this, boost::placeholders::_1)); zhttpRequest->beginResponse(200, "OK", headers); @@ -1072,7 +1087,7 @@ if(autoCrossOrigin) Cors::applyCorsHeaders(requestData.headers, &responseData.headers); - bytesWrittenConnection = zhttpRequest->bytesWritten.connect(boost::bind(&Private::zhttpRequest_bytesWritten, this, boost::placeholders::_1)); + zhttpReqConnections.bytesWrittenConnection = zhttpRequest->bytesWritten.connect(boost::bind(&Private::zhttpRequest_bytesWritten, this, boost::placeholders::_1)); zhttpRequest->beginResponse(responseData.code, responseData.reason, responseData.headers); } @@ -1119,7 +1134,7 @@ // if we error while streaming, all we can do is give up zhttpRequest->endBody(); - emit q->errorResponding(); + q->errorResponding(); return; } @@ -1166,20 +1181,19 @@ void doInspectError() { state = WaitingForResponse; - emit q->inspectError(); + q->inspectError(); } -}; -RequestSession::RequestSession(StatsManager *stats, QObject *parent) : - QObject(parent) -{ - d = new Private(this, 0, 0, 0, 0, 0, stats); -} + void doFinished() + { + q->finished(); + } +}; -RequestSession::RequestSession(DomainMap *domainMap, SockJsManager *sockJsManager, ZrpcManager *inspectManager, ZrpcChecker *inspectChecker, ZrpcManager *acceptManager, StatsManager *stats, QObject *parent) : +RequestSession::RequestSession(int workerId, DomainMap *domainMap, SockJsManager *sockJsManager, ZrpcManager *inspectManager, ZrpcChecker *inspectChecker, ZrpcManager *acceptManager, StatsManager *stats, QObject *parent) : QObject(parent) { - d = new Private(this, domainMap, sockJsManager, inspectManager, inspectChecker, acceptManager, stats); + d = new Private(this, workerId, domainMap, sockJsManager, inspectManager, inspectChecker, acceptManager, stats); } RequestSession::~RequestSession() @@ -1323,7 +1337,7 @@ d->start(req); } -void RequestSession::startRetry(ZhttpRequest *req, bool debug, bool autoCrossOrigin, const QByteArray &jsonpCallback, bool jsonpExtendedResponse, int unreportedTime) +void RequestSession::startRetry(ZhttpRequest *req, bool debug, bool autoCrossOrigin, const QByteArray &jsonpCallback, bool jsonpExtendedResponse, int unreportedTime, int retrySeq) { d->isRetry = true; d->zhttpRequest = req; @@ -1337,7 +1351,7 @@ d->requestData.headers = req->requestHeaders(); d->requestData.body = req->readBody(); - d->startRetry(unreportedTime); + d->startRetry(unreportedTime, retrySeq); } void RequestSession::pause() @@ -1357,7 +1371,7 @@ { assert(d->state == Private::ReceivingForAccept || d->state == Private::WaitingForResponse); - emit headerBytesSent(ZhttpManager::estimateResponseHeaderBytes(code, reason, headers)); + headerBytesSent(ZhttpManager::estimateResponseHeaderBytes(code, reason, headers)); d->state = Private::RespondingStart; d->responseData.code = code; @@ -1372,7 +1386,7 @@ assert(d->state == Private::RespondingStart || d->state == Private::Responding); assert(!d->responseBodyFinished); - emit bodyBytesSent(body.size()); + bodyBytesSent(body.size()); d->out += body; d->responseUpdate(); diff -Nru pushpin-1.38.0/src/cpp/proxy/requestsession.h pushpin-1.39.1/src/cpp/proxy/requestsession.h --- pushpin-1.38.0/src/cpp/proxy/requestsession.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/requestsession.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2023 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -28,6 +29,8 @@ #include "domainmap.h" #include +using Signal = boost::signals2::signal; +using SignalInt = boost::signals2::signal; using Connection = boost::signals2::scoped_connection; class QHostAddress; @@ -51,8 +54,7 @@ Q_OBJECT public: - RequestSession(StatsManager *stats, QObject *parent = 0); - RequestSession(DomainMap *domainMap, SockJsManager *sockJsManager, ZrpcManager *inspectManager, ZrpcChecker *inspectChecker, ZrpcManager *accept, StatsManager *stats, QObject *parent = 0); + RequestSession(int workerId, DomainMap *domainMap, SockJsManager *sockJsManager, ZrpcManager *inspectManager, ZrpcChecker *inspectChecker, ZrpcManager *accept, StatsManager *stats, QObject *parent = 0); ~RequestSession(); bool isRetry() const; @@ -86,7 +88,7 @@ // takes ownership void start(ZhttpRequest *req); - void startRetry(ZhttpRequest *req, bool debug, bool autoCrossOrigin, const QByteArray &jsonpCallback, bool jsonpExtendedResponse, int unreportedTime); + void startRetry(ZhttpRequest *req, bool debug, bool autoCrossOrigin, const QByteArray &jsonpCallback, bool jsonpExtendedResponse, int unreportedTime, int retrySeq); void pause(); void resume(); @@ -101,21 +103,19 @@ int unregisterConnection(); // return unreported time -signals: - void inspected(const InspectData &idata); - void inspectError(); - void finished(); - void finishedByAccept(); - void bytesWritten(int count); - void paused(); - void headerBytesSent(int count); - void bodyBytesSent(int count); - + Signal inspectError; + boost::signals2::signal inspected; + Signal finishedByAccept; + SignalInt bytesWritten; + Signal paused; + SignalInt headerBytesSent; + SignalInt bodyBytesSent; // this signal means some error was encountered while responding and // that you should not attempt to call further response-related // methods. the object remains in an active state though, and so you // should still wait for finished() - void errorResponding(); + Signal errorResponding; + Signal finished; private: class Private; diff -Nru pushpin-1.38.0/src/cpp/proxy/sockjsmanager.cpp pushpin-1.39.1/src/cpp/proxy/sockjsmanager.cpp --- pushpin-1.38.0/src/cpp/proxy/sockjsmanager.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/sockjsmanager.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2015-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -31,12 +32,15 @@ #include #include #include +#include "qtcompat.h" #include "log.h" #include "bufferlist.h" #include "zhttprequest.h" #include "zwebsocket.h" #include "sockjssession.h" +using std::map; + #define MAX_REQUEST_BODY 100000 const char *iframeHtmlTemplate = @@ -110,7 +114,12 @@ ~Session() { - delete req; + if(req) + { + owner->reqConnectionMap.erase(req); + delete req; + } + owner->wsConnectionMap.erase(sock); delete sock; if(timer) @@ -122,6 +131,17 @@ } }; + struct WSConnections { + Connection closedConnection; + Connection errorConnection; + }; + + struct ZhttpReqConnections{ + Connection readyReadConnection; + Connection bytesWrittenConnection; + Connection errorConnection; + }; + SockJsManager *q; QSet sessions; QHash sessionsByRequest; @@ -133,9 +153,8 @@ QByteArray iframeHtml; QByteArray iframeHtmlEtag; QSet discardedRequests; - Connection readyReadConnection; - Connection bytesWrittenConnection; - Connection errorConnection; + map reqConnectionMap; + map wsConnectionMap; Private(SockJsManager *_q, const QString &sockJsUrl) : QObject(_q), @@ -188,7 +207,10 @@ { // if there's a close value, hang around for a little bit s->timer = new QTimer(this); - connect(s->timer, &QTimer::timeout, this, &Private::timer_timeout); + QObject::connect(s->timer, &QTimer::timeout, [this, timer=s->timer]() { + this->timer_timeout(timer); + }); + s->timer->setSingleShot(true); sessionsByTimer.insert(s->timer, s); s->timer->start(5000); @@ -241,9 +263,11 @@ s->route = route; - readyReadConnection = req->readyRead.connect(boost::bind(&Private::req_readyRead, this)); - bytesWrittenConnection = req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1)); - errorConnection = req->error.connect(boost::bind(&Private::req_error, this)); + reqConnectionMap[req] = { + req->readyRead.connect(boost::bind(&Private::req_readyRead, this, req)), + req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1, req)), + req->error.connect(boost::bind(&Private::req_error, this, req)) + }; sessions += s; sessionsByRequest.insert(s->req, s); @@ -265,8 +289,10 @@ s->asUri.setPath(QString::fromUtf8(encPath.mid(0, basePathStart) + "/websocket"), QUrl::StrictMode); s->route = route; - connect(sock, &ZWebSocket::closed, this, &Private::sock_closed); - connect(sock, &ZWebSocket::error, this, &Private::sock_error); + wsConnectionMap[sock] = { + sock->closed.connect(boost::bind(&Private::sock_closed, this, sock)), + sock->error.connect(boost::bind(&Private::sock_error, this, sock)) + }; sessions += s; sessionsBySocket.insert(s->sock, s); @@ -316,7 +342,7 @@ if(data.isValid()) { QJsonDocument doc; - if(data.type() == QVariant::Map) + if(typeId(data) == QMetaType::QVariantMap) doc = QJsonDocument(QJsonObject::fromVariantMap(data.toMap())); else // List doc = QJsonDocument(QJsonArray::fromVariantList(data.toList())); @@ -364,9 +390,11 @@ { discardedRequests += req; - readyReadConnection = req->readyRead.connect(boost::bind(&Private::req_readyRead, this)); - bytesWrittenConnection = req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1)); - errorConnection = req->error.connect(boost::bind(&Private::req_error, this)); + reqConnectionMap[req] = { + req->readyRead.connect(boost::bind(&Private::req_readyRead, this, req)), + req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1, req)), + req->error.connect(boost::bind(&Private::req_error, this, req)) + }; } HttpHeaders headers; @@ -448,7 +476,7 @@ ZhttpRequest *req = s->req; QByteArray body = s->reqBody.toByteArray(); QByteArray jsonpCallback = s->jsonpCallback; - s->req->disconnect(this); + reqConnectionMap.erase(req); s->req = 0; removeSession(s); @@ -484,7 +512,7 @@ sessionsById.insert(s->sid, s); s->pending = true; pendingSessions += s; - emit q->sessionReady(); + q->sessionReady(); return; } } @@ -499,7 +527,7 @@ { s->pending = true; pendingSessions += s; - emit q->sessionReady(); + q->sessionReady(); return; } else @@ -514,7 +542,7 @@ s->lastPart = lastPart; s->pending = true; pendingSessions += s; - emit q->sessionReady(); + q->sessionReady(); return; } @@ -551,7 +579,7 @@ s->ext->setupServer(q, s->req, s->jsonpCallback, s->asUri, s->sid, s->lastPart, s->reqBody.toByteArray(), s->route); - s->req->disconnect(this); + reqConnectionMap.erase(s->req); sessionsByRequest.remove(s->req); s->req = 0; } @@ -565,7 +593,7 @@ else s->ext->setupServer(q, s->sock, s->asUri, s->route); - s->sock->disconnect(this); + wsConnectionMap.erase(s->sock); sessionsBySocket.remove(s->sock); s->sock = 0; } @@ -575,11 +603,9 @@ return s->ext; } -private slots: - void req_readyRead() +private: + void req_readyRead(ZhttpRequest *req) { - ZhttpRequest *req = (ZhttpRequest *)sender(); - // for a request to have been discardable, we must have read the // entire input already and handed to the session assert(!discardedRequests.contains(req)); @@ -590,17 +616,16 @@ processRequestInput(s); } - void req_bytesWritten(int count) + void req_bytesWritten(int count, ZhttpRequest *req) { Q_UNUSED(count); - ZhttpRequest *req = (ZhttpRequest *)sender(); - if(discardedRequests.contains(req)) { if(req->isFinished()) { discardedRequests.remove(req); + reqConnectionMap.erase(req); delete req; } @@ -617,13 +642,12 @@ } } - void req_error() + void req_error(ZhttpRequest *req) { - ZhttpRequest *req = (ZhttpRequest *)sender(); - if(discardedRequests.contains(req)) { discardedRequests.remove(req); + reqConnectionMap.erase(req); delete req; return; } @@ -637,9 +661,8 @@ removeSession(s); } - void sock_closed() + void sock_closed(ZWebSocket *sock) { - ZWebSocket *sock = (ZWebSocket *)sender(); Session *s = sessionsBySocket.value(sock); assert(s); @@ -649,9 +672,8 @@ removeSession(s); } - void sock_error() + void sock_error(ZWebSocket *sock) { - ZWebSocket *sock = (ZWebSocket *)sender(); Session *s = sessionsBySocket.value(sock); assert(s); @@ -661,9 +683,9 @@ removeSession(s); } - void timer_timeout() +private: + void timer_timeout(QTimer *timer) { - QTimer *timer = (QTimer *)sender(); Session *s = sessionsByTimer.value(timer); assert(s); diff -Nru pushpin-1.38.0/src/cpp/proxy/sockjssession.cpp pushpin-1.39.1/src/cpp/proxy/sockjssession.cpp --- pushpin-1.38.0/src/cpp/proxy/sockjssession.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/sockjssession.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2015-2021 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -30,6 +30,7 @@ #include #include #include +#include "qtcompat.h" #include "log.h" #include "bufferlist.h" #include "packet/httprequestdata.h" @@ -37,6 +38,8 @@ #include "zwebsocket.h" #include "sockjsmanager.h" +using std::map; + #define BUFFER_SIZE 200000 #define KEEPALIVE_TIMEOUT 25 #define UNCONNECTED_TIMEOUT 5 @@ -113,6 +116,20 @@ } }; + struct WSConnections { + Connection readyReadConnection; + Connection framesWrittenConnection; + Connection writeBytesChangedConnection; + Connection closedConnection; + Connection peerClosedConnection; + Connection sockErrorConnection; + }; + + struct ReqConnections { + Connection bytesWrittenConnection; + Connection errorConnection; + }; + SockJsSession *q; SockJsManager *manager; Mode mode; @@ -146,8 +163,8 @@ int peerCloseCode; QString peerCloseReason; bool updating; - Connection bytesWrittenConnection; - Connection errorConnection; + map reqConnectionMap; + WSConnections wsConnection; Private(SockJsSession *_q) : QObject(_q), @@ -184,6 +201,7 @@ void removeRequestItem(RequestItem *ri) { + reqConnectionMap.erase(ri->req); requests.remove(ri->req); delete ri; } @@ -213,7 +231,8 @@ assert(ri); // detach req from RequestItem - requests.remove(req); + reqConnectionMap.erase(ri->req); + requests.remove(ri->req); ri->req = 0; delete ri; @@ -231,6 +250,7 @@ } requests.clear(); + wsConnection = WSConnections(); delete sock; sock = 0; @@ -256,17 +276,21 @@ requests.insert(req, new RequestItem(req, jsonpCallback, RequestItem::Connect)); - bytesWrittenConnection = req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1)); - errorConnection = req->error.connect(boost::bind(&Private::req_error, this)); + reqConnectionMap[req] = { + req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1, req)), + req->error.connect(boost::bind(&Private::req_error, this, req)) + }; } else { - connect(sock, &ZWebSocket::readyRead, this, &Private::sock_readyRead); - connect(sock, &ZWebSocket::framesWritten, this, &Private::sock_framesWritten); - connect(sock, &ZWebSocket::writeBytesChanged, this, &Private::sock_writeBytesChanged); - connect(sock, &ZWebSocket::closed, this, &Private::sock_closed); - connect(sock, &ZWebSocket::peerClosed, this, &Private::sock_peerClosed); - connect(sock, &ZWebSocket::error, this, &Private::sock_error); + wsConnection = WSConnections{ + sock->readyRead.connect(boost::bind(&Private::sock_readyRead, this)), + sock->framesWritten.connect(boost::bind(&Private::sock_framesWritten, this, boost::placeholders::_1, boost::placeholders::_2)), + sock->writeBytesChanged.connect(boost::bind(&Private::sock_writeBytesChanged, this)), + sock->closed.connect(boost::bind(&Private::sock_closed, this)), + sock->peerClosed.connect(boost::bind(&Private::sock_peerClosed, this)), + sock->error.connect(boost::bind(&Private::sock_error, this)) + }; } } @@ -297,8 +321,10 @@ void handleRequest(ZhttpRequest *_req, const QByteArray &jsonpCallback, const QByteArray &lastPart, const QByteArray &body) { - bytesWrittenConnection = _req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1)); - errorConnection = _req->error.connect(boost::bind(&Private::req_error, this)); + reqConnectionMap[_req] = { + _req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1, _req)), + _req->error.connect(boost::bind(&Private::req_error, this, _req)) + }; if(lastPart == "xhr" || lastPart == "jsonp") { @@ -357,6 +383,7 @@ int at = kv.indexOf('='); if(at == -1) continue; + if(QUrl::fromPercentEncoding(kv.mid(0, at)) == "d") { param = QUrl::fromPercentEncoding(kv.mid(at + 1)).toUtf8(); @@ -386,7 +413,7 @@ int bytes = 0; foreach(const QVariant &vmessage, messages) { - if(vmessage.type() != QVariant::String) + if(typeId(vmessage) != QMetaType::QString) { requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true)); respondError(_req, 400, "Bad Request", "Payload expected"); @@ -419,6 +446,11 @@ tryRead(); } + else + { + requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true)); + respondError(_req, 404, "Not Found", "Not Found"); + } } void accept(const QByteArray &reason, const HttpHeaders &headers) @@ -560,7 +592,7 @@ state = Idle; applyLinger(); cleanup(); - QMetaObject::invokeMethod(q, "closed", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "doClosed", Qt::QueuedConnection); } else tryWrite(); @@ -622,7 +654,7 @@ if(bytes > 0) { QPointer self = this; - emit q->writeBytesChanged(); + q->writeBytesChanged(); if(!self) return; } @@ -692,7 +724,7 @@ if(emitReadyRead) { - emit q->readyRead(); + q->readyRead(); if(!self) return false; } @@ -767,7 +799,7 @@ int bytes = 0; foreach(const QVariant &vmessage, messages) { - if(vmessage.type() != QVariant::String) + if(typeId(vmessage) != QMetaType::QString) { error = true; break; @@ -799,7 +831,7 @@ { state = Idle; cleanup(); - emit q->error(); + q->error(); // stop signals return false; @@ -807,7 +839,7 @@ if(emitReadyRead) { - emit q->readyRead(); + q->readyRead(); if(!self) return false; } @@ -850,7 +882,7 @@ pendingWrittenBytes = 0; } - emit q->framesWritten(count, contentBytes); + q->framesWritten(count, contentBytes); } QVariant applyLinger() @@ -871,12 +903,10 @@ return closeValue; } -private slots: - void req_bytesWritten(int count) + void req_bytesWritten(int count, ZhttpRequest *_req) { Q_UNUSED(count); - ZhttpRequest *_req = (ZhttpRequest *)sender(); RequestItem *ri = requests.value(_req); assert(ri); @@ -903,7 +933,7 @@ state = Idle; removeRequestItem(ri); cleanup(); - emit q->closed(); + q->closed(); return; } else if(ri->type == RequestItem::Receive) @@ -920,7 +950,7 @@ state = Idle; removeRequestItem(ri); cleanup(); - emit q->closed(); + q->closed(); return; } } @@ -929,9 +959,8 @@ } } - void req_error() + void req_error(ZhttpRequest *_req) { - ZhttpRequest *_req = (ZhttpRequest *)sender(); RequestItem *ri = requests.value(_req); assert(ri); @@ -954,7 +983,7 @@ if(close && !peerClosed) { peerClosed = true; - emit q->peerClosed(); + q->peerClosed(); return; } @@ -962,9 +991,9 @@ cleanup(); if(close) - emit q->closed(); + q->closed(); else - emit q->error(); + q->error(); } else { @@ -980,7 +1009,7 @@ } else // WebSocketPassthrough { - emit q->readyRead(); + q->readyRead(); } } @@ -991,14 +1020,14 @@ void sock_writeBytesChanged() { - emit q->writeBytesChanged(); + q->writeBytesChanged(); } void sock_peerClosed() { peerCloseCode = sock->peerCloseCode(); peerCloseReason = sock->peerCloseReason(); - emit q->peerClosed(); + q->peerClosed(); } void sock_closed() @@ -1007,7 +1036,7 @@ peerCloseReason = sock->peerCloseReason(); state = Idle; cleanup(); - emit q->closed(); + q->closed(); } void sock_error() @@ -1015,9 +1044,10 @@ state = Idle; errorCondition = sock->errorCondition(); cleanup(); - emit q->error(); + q->error(); } +private slots: void doUpdate() { updating = false; @@ -1026,7 +1056,7 @@ { state = Idle; cleanup(); - emit q->error(); + q->error(); return; } @@ -1042,11 +1072,16 @@ pendingWrittenFrames = 0; pendingWrittenBytes = 0; - emit q->framesWritten(count, contentBytes); + q->framesWritten(count, contentBytes); } } } + void doClosed() + { + q->closed(); + } + void keepAliveTimer_timeout() { assert(mode != WebSocketPassthrough); @@ -1066,7 +1101,7 @@ // timeout while unconnected state = Idle; cleanup(); - emit q->error(); + q->error(); } } else diff -Nru pushpin-1.38.0/src/cpp/proxy/testhttprequest.cpp pushpin-1.39.1/src/cpp/proxy/testhttprequest.cpp --- pushpin-1.38.0/src/cpp/proxy/testhttprequest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/testhttprequest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -64,6 +64,10 @@ } public slots: + void doBytesWritten(int cnt){ + q->bytesWritten(cnt); + } + void processRequest() { if(!requestBodyFinished) @@ -74,7 +78,7 @@ responseBody += QByteArray("request too large\n"); state = Responded; - emit q->readyRead(); + q->readyRead(); return; } @@ -131,7 +135,7 @@ } state = Responded; - emit q->readyRead(); + q->readyRead(); } }; @@ -215,7 +219,7 @@ { d->requestBody += buf; - QMetaObject::invokeMethod(this, "bytesWritten", Qt::QueuedConnection, Q_ARG(int, buf.size())); + QMetaObject::invokeMethod(this, "doBytesWritten", Qt::QueuedConnection, Q_ARG(int, buf.size())); } } } diff -Nru pushpin-1.38.0/src/cpp/proxy/testwebsocket.cpp pushpin-1.39.1/src/cpp/proxy/testwebsocket.cpp --- pushpin-1.38.0/src/cpp/proxy/testwebsocket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/testwebsocket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -115,10 +115,10 @@ } } - emit q->connected(); + q->connected(); if(gripEnabled && !channels.isEmpty()) - QMetaObject::invokeMethod(q, "readyRead", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "doReadyRead", Qt::QueuedConnection); } else { @@ -128,14 +128,29 @@ response.body += QByteArray("no such test resource\n"); errorCondition = ErrorRejected; - emit q->error(); + q->error(); } } + void doReadyRead() + { + q->readyRead(); + } + + void doFramesWritten(int count, int bytes) + { + q->framesWritten(count, bytes); + } + + void doWriteBytesChanged() + { + q->writeBytesChanged(); + } + void handleClose() { state = Idle; - emit q->closed(); + q->closed(); } }; @@ -296,13 +311,13 @@ d->inFrames += tmp; - QMetaObject::invokeMethod(this, "framesWritten", Qt::QueuedConnection, Q_ARG(int, 1), Q_ARG(int, tmp.data.size())); - QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection); + QMetaObject::invokeMethod(d, "doFramesWritten", Qt::QueuedConnection, Q_ARG(int, 1), Q_ARG(int, tmp.data.size())); + QMetaObject::invokeMethod(d, "doReadyRead", Qt::QueuedConnection); } WebSocket::Frame TestWebSocket::readFrame() { - QMetaObject::invokeMethod(this, "writeBytesChanged", Qt::QueuedConnection); + QMetaObject::invokeMethod(d, "doWriteBytesChanged", Qt::QueuedConnection); return d->inFrames.takeFirst(); } diff -Nru pushpin-1.38.0/src/cpp/proxy/updater.cpp pushpin-1.39.1/src/cpp/proxy/updater.cpp --- pushpin-1.38.0/src/cpp/proxy/updater.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/updater.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2015-2017 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -30,6 +31,7 @@ #include #include #include +#include "qtcompat.h" #include "log.h" #include "httpheaders.h" #include "zhttpmanager.h" @@ -70,6 +72,11 @@ Q_OBJECT public: + struct ReqConnections { + Connection readyReadConnection; + Connection errorConnection; + }; + Updater *q; Mode mode; bool quiet; @@ -80,8 +87,7 @@ ZhttpRequest *req; Report report; QDateTime lastLogTime; - Connection readyReadConnection; - Connection errorConnection; + ReqConnections reqConnections; Private(Updater *_q, Mode _mode, bool _quiet, const QString &_currentVersion, const QString &_org, ZhttpManager *zhttp) : QObject(_q), @@ -110,6 +116,7 @@ void cleanupRequest() { + reqConnections = ReqConnections(); delete req; req = 0; } @@ -119,8 +126,10 @@ { req = zhttpManager->createRequest(); req->setParent(this); - readyReadConnection = req->readyRead.connect(boost::bind(&Private::req_readyRead, this)); - errorConnection = req->error.connect(boost::bind(&Private::req_error, this)); + reqConnections = { + req->readyRead.connect(boost::bind(&Private::req_readyRead, this)), + req->error.connect(boost::bind(&Private::req_error, this)) + }; req->setIgnorePolicies(true); req->setIgnoreTlsErrors(true); @@ -205,10 +214,10 @@ QVariantMap body = doc.object().toVariantMap(); - if(body.contains("updates") && body["updates"].type() == QVariant::List) + if(body.contains("updates") && typeId(body["updates"]) == QMetaType::QVariantList) { QVariantList updates = body["updates"].toList(); - if(!updates.isEmpty() && updates[0].type() == QVariant::Map) + if(!updates.isEmpty() && typeId(updates[0]) == QMetaType::QVariantMap) { QVariantMap update = updates[0].toMap(); QString version = update.value("version").toString(); diff -Nru pushpin-1.38.0/src/cpp/proxy/websocketoverhttp.cpp pushpin-1.39.1/src/cpp/proxy/websocketoverhttp.cpp --- pushpin-1.38.0/src/cpp/proxy/websocketoverhttp.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/websocketoverhttp.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2014-2022 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -26,7 +26,6 @@ #include #include #include -#include #include #include "log.h" #include "bufferlist.h" @@ -69,7 +68,13 @@ { Q_OBJECT - map disconnectedConnection; + struct WSConnections { + Connection disconnectedConnection; + Connection closedConnection; + Connection errorConnection; + }; + + map wsConnectionMap; public: DisconnectManager(QObject *parent = 0) : @@ -80,9 +85,11 @@ void addSocket(WebSocketOverHttp *sock) { sock->setParent(this); - disconnectedConnection[sock] = sock->disconnected.connect(boost::bind(&DisconnectManager::sock_disconnected, this, sock)); - connect(sock, &WebSocketOverHttp::closed, this, &DisconnectManager::sock_closed); - connect(sock, &WebSocketOverHttp::error, this, &DisconnectManager::sock_error); + wsConnectionMap[sock] = { + sock->disconnected.connect(boost::bind(&DisconnectManager::sock_disconnected, this, sock)), + sock->closed.connect(boost::bind(&DisconnectManager::sock_closed, this, sock)), + sock->error.connect(boost::bind(&DisconnectManager::sock_error, this, sock)) + }; sock->sendDisconnect(); } @@ -95,7 +102,7 @@ private: void cleanupSocket(WebSocketOverHttp *sock) { - disconnectedConnection.erase(sock); + wsConnectionMap.erase(sock); delete sock; } @@ -105,22 +112,19 @@ cleanupSocket(sock); } -private slots: - void sock_closed() + void sock_closed(WebSocketOverHttp *sock) { - WebSocketOverHttp *sock = (WebSocketOverHttp *)sender(); cleanupSocket(sock); } - void sock_error() + void sock_error(WebSocketOverHttp *sock) { - WebSocketOverHttp *sock = (WebSocketOverHttp *)sender(); cleanupSocket(sock); } }; -WebSocketOverHttp::DisconnectManager *WebSocketOverHttp::g_disconnectManager = 0; -int WebSocketOverHttp::g_maxManagedDisconnects = -1; +thread_local WebSocketOverHttp::DisconnectManager *WebSocketOverHttp::g_disconnectManager = 0; +thread_local int WebSocketOverHttp::g_maxManagedDisconnects = -1; static QList decodeEvents(const QByteArray &in, bool *ok = 0) { @@ -189,6 +193,12 @@ Q_OBJECT public: + struct ReqConnections { + Connection readyReadConnection; + Connection bytesWrittenConnection; + Connection errorConnection; + }; + WebSocketOverHttp *q; ZhttpManager *zhttpManager; QString connectHost; @@ -227,9 +237,7 @@ QTimer *retryTimer; int retries; int maxEvents; - Connection readyReadConnection; - Connection bytesWrittenConnection; - Connection errorConnection; + ReqConnections reqConnections; Private(WebSocketOverHttp *_q) : QObject(_q), @@ -259,7 +267,7 @@ maxEvents(0) { if(!g_disconnectManager) - g_disconnectManager = new DisconnectManager(QCoreApplication::instance()); + g_disconnectManager = new DisconnectManager; keepAliveTimer = new QTimer(this); connect(keepAliveTimer, &QTimer::timeout, this, &Private::keepAliveTimer_timeout); @@ -290,6 +298,7 @@ disconnecting = false; updateQueued = false; + reqConnections = ReqConnections(); delete req; req = 0; @@ -611,9 +620,11 @@ req = zhttpManager->createRequest(); req->setParent(this); - readyReadConnection = req->readyRead.connect(boost::bind(&Private::req_readyRead, this)); - bytesWrittenConnection = req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1)); - errorConnection = req->error.connect(boost::bind(&Private::req_error, this)); + reqConnections = { + req->readyRead.connect(boost::bind(&Private::req_readyRead, this)), + req->bytesWritten.connect(boost::bind(&Private::req_bytesWritten, this, boost::placeholders::_1)), + req->error.connect(boost::bind(&Private::req_error, this)) + }; if(!connectHost.isEmpty()) req->setConnectHost(connectHost); @@ -641,13 +652,12 @@ req->endBody(); } -private slots: void req_readyRead() { if(inBuf.size() + req->bytesAvailable() > RESPONSE_BODY_MAX) { cleanup(); - emit q->error(); + q->error(); return; } @@ -667,6 +677,7 @@ HttpHeaders responseHeaders = req->responseHeaders(); QByteArray responseBody = inBuf.take(); + reqConnections = ReqConnections(); delete req; req = 0; @@ -691,7 +702,7 @@ errorCondition = ErrorGeneric; cleanup(); - emit q->error(); + q->error(); return; } @@ -727,7 +738,7 @@ if(!ok) { cleanup(); - emit q->error(); + q->error(); return; } @@ -737,7 +748,7 @@ if(events.isEmpty() && keepAliveInterval == -1) { cleanup(); - emit q->error(); + q->error(); return; } @@ -745,7 +756,7 @@ if(!events.isEmpty() && events.first().type != "OPEN") { cleanup(); - emit q->error(); + q->error(); return; } @@ -838,21 +849,21 @@ if(emitConnected) { - emit q->connected(); + q->connected(); if(!self) return; } if(emitReadyRead) { - emit q->readyRead(); + q->readyRead(); if(!self) return; } if(reqFrames > 0) { - emit q->framesWritten(reqFrames, reqContentSize); + q->framesWritten(reqFrames, reqContentSize); if(!self) return; } @@ -864,7 +875,7 @@ if(hadContent) { - emit q->writeBytesChanged(); + q->writeBytesChanged(); if(!self) return; } @@ -877,12 +888,12 @@ if(closeSent) { cleanup(); - emit q->closed(); + q->closed(); return; } else { - emit q->peerClosed(); + q->peerClosed(); } } else if(closeSent && keepAliveInterval == -1) @@ -896,14 +907,14 @@ if(disconnected) { cleanup(); - emit q->error(); + q->error(); return; } if(reqClose && peerClosing) { cleanup(); - emit q->closed(); + q->closed(); return; } @@ -947,6 +958,7 @@ break; } + reqConnections = ReqConnections(); delete req; req = 0; @@ -978,9 +990,10 @@ errorCondition = WebSocket::ErrorTls; cleanup(); - emit q->error(); + q->error(); } +private slots: void keepAliveTimer_timeout() { update(); @@ -996,7 +1009,7 @@ cleanup(); errorCondition = pendingErrorCondition; pendingErrorCondition = (ErrorCondition)-1; - emit q->error(); + q->error(); } }; diff -Nru pushpin-1.38.0/src/cpp/proxy/websocketoverhttp.h pushpin-1.39.1/src/cpp/proxy/websocketoverhttp.h --- pushpin-1.38.0/src/cpp/proxy/websocketoverhttp.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/websocketoverhttp.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2014-2020 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -46,6 +46,7 @@ void setMaxEventsPerRequest(int max); void refresh(); + // disconnection management is thread local static void setMaxManagedDisconnects(int max); static void clearDisconnectManager(); @@ -97,8 +98,8 @@ friend class Private; Private *d; - static DisconnectManager *g_disconnectManager; - static int g_maxManagedDisconnects; + static thread_local DisconnectManager *g_disconnectManager; + static thread_local int g_maxManagedDisconnects; }; #endif diff -Nru pushpin-1.38.0/src/cpp/proxy/wscontrolmanager.cpp pushpin-1.39.1/src/cpp/proxy/wscontrolmanager.cpp --- pushpin-1.38.0/src/cpp/proxy/wscontrolmanager.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/wscontrolmanager.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2020 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -28,6 +29,7 @@ #include #include "qzmqsocket.h" #include "qzmqvalve.h" +#include "qzmqreqmessage.h" #include "log.h" #include "tnetstring.h" #include "zutil.h" @@ -59,26 +61,28 @@ }; WsControlManager *q; + QByteArray identity; int ipcFileMode; - QString inSpec; - QString outSpec; - QZmq::Socket *inSock; - QZmq::Socket *outSock; - QZmq::Valve *inValve; + QStringList initSpecs; + QStringList streamSpecs; + QZmq::Socket *initSock; + QZmq::Socket *streamSock; + QZmq::Valve *streamValve; QHash sessionsByCid; QTimer *refreshTimer; QHash keepAliveRegistrations; QMap, KeepAliveRegistration*> sessionsByLastRefresh; QSet sessionRefreshBuckets[SESSION_REFRESH_BUCKETS]; int currentSessionRefreshBucket; + Connection streamValveConnection; Private(WsControlManager *_q) : QObject(_q), q(_q), ipcFileMode(-1), - inSock(0), - outSock(0), - inValve(0), + initSock(0), + streamSock(0), + streamValve(0), currentSessionRefreshBucket(0) { refreshTimer = new QTimer(this); @@ -95,45 +99,53 @@ refreshTimer->deleteLater(); } - bool setupIn() + bool setupInit() { - delete inSock; + delete initSock; - inSock = new QZmq::Socket(QZmq::Socket::Pull, this); + initSock = new QZmq::Socket(QZmq::Socket::Push, this); - inSock->setHwm(DEFAULT_HWM); + initSock->setHwm(DEFAULT_HWM); + initSock->setShutdownWaitTime(0); - QString errorMessage; - if(!ZUtil::setupSocket(inSock, inSpec, true, ipcFileMode, &errorMessage)) + foreach(const QString &spec, initSpecs) { - log_error("%s", qPrintable(errorMessage)); - return false; + QString errorMessage; + if(!ZUtil::setupSocket(initSock, spec, true, ipcFileMode, &errorMessage)) + { + log_error("%s", qPrintable(errorMessage)); + return false; + } } - inValve = new QZmq::Valve(inSock, this); - connect(inValve, &QZmq::Valve::readyRead, this, &Private::in_readyRead); - - inValve->open(); - return true; } - bool setupOut() + bool setupStream() { - delete outSock; + delete streamSock; - outSock = new QZmq::Socket(QZmq::Socket::Push, this); + streamSock = new QZmq::Socket(QZmq::Socket::Router, this); - outSock->setHwm(DEFAULT_HWM); - outSock->setShutdownWaitTime(0); + streamSock->setIdentity(identity); + streamSock->setHwm(DEFAULT_HWM); + streamSock->setShutdownWaitTime(0); - QString errorMessage; - if(!ZUtil::setupSocket(outSock, outSpec, true, ipcFileMode, &errorMessage)) + foreach(const QString &spec, streamSpecs) { - log_error("%s", qPrintable(errorMessage)); - return false; + QString errorMessage; + if(!ZUtil::setupSocket(streamSock, spec, true, ipcFileMode, &errorMessage)) + { + log_error("%s", qPrintable(errorMessage)); + return false; + } } + streamValve = new QZmq::Valve(streamSock, this); + streamValveConnection = streamValve->readyRead.connect(boost::bind(&Private::stream_readyRead, this, boost::placeholders::_1)); + + streamValve->open(); + return true; } @@ -154,9 +166,9 @@ return best; } - void write(const WsControlPacket &packet) + void writeInit(const WsControlPacket &packet) { - assert(outSock); + assert(streamSock); QVariant vpacket = packet.toVariant(); QByteArray buf = TnetString::fromVariant(vpacket); @@ -164,14 +176,40 @@ if(log_outputLevel() >= LOG_LEVEL_DEBUG) LogUtil::logVariant(LOG_LEVEL_DEBUG, vpacket, "wscontrol: OUT"); - outSock->write(QList() << buf); + initSock->write(QList() << buf); } - void write(const WsControlPacket::Item &item) + void writeStream(const WsControlPacket &packet, const QByteArray &instanceAddress) + { + assert(streamSock); + + QVariant vpacket = packet.toVariant(); + QByteArray buf = TnetString::fromVariant(vpacket); + + if(log_outputLevel() >= LOG_LEVEL_DEBUG) + LogUtil::logVariant(LOG_LEVEL_DEBUG, vpacket, "wscontrol: OUT to=%s", instanceAddress.data()); + + QList msg; + msg += instanceAddress; + msg += QByteArray(); + msg += buf; + streamSock->write(msg); + } + + void writeInit(const WsControlPacket::Item &item) { WsControlPacket out; + out.from = identity; out.items += item; - write(out); + writeInit(out); + } + + void writeStream(const WsControlPacket::Item &item, const QByteArray &instanceAddress) + { + WsControlPacket out; + out.from = identity; + out.items += item; + writeStream(out, instanceAddress); } void registerKeepAlive(WsControlSession *s) @@ -219,16 +257,18 @@ refreshTimer->stop(); } -private slots: - void in_readyRead(const QList &message) +private: + void stream_readyRead(const QList &message) { - if(message.count() != 1) + QZmq::ReqMessage req(message); + + if(req.content().count() != 1) { log_warning("wscontrol: received message with parts != 1, skipping"); return; } - QVariant data = TnetString::toVariant(message[0]); + QVariant data = TnetString::toVariant(req.content()[0]); if(data.isNull()) { log_warning("wscontrol: received message with invalid format (tnetstring parse failed), skipping"); @@ -245,6 +285,12 @@ return; } + if(p.from.isEmpty()) + { + log_warning("wscontrol: received message with invalid from value, skipping"); + return; + } + QPointer self = this; foreach(const WsControlPacket::Item &i, p.items) @@ -260,24 +306,25 @@ WsControlPacket::Item out; out.cid = i.cid; out.type = WsControlPacket::Item::Cancel; - write(out); + writeStream(out, p.from); } continue; } - s->handle(i); + s->handle(p.from, i); if(!self) return; } } +private slots: void refresh_timeout() { qint64 now = QDateTime::currentMSecsSinceEpoch(); - WsControlPacket packet; + QHash packets; // process the current bucket const QSet &bucket = sessionRefreshBuckets[currentSessionRefreshBucket]; @@ -289,6 +336,19 @@ r->lastRefresh = now; sessionsByLastRefresh.insert(QPair(r->lastRefresh, r), r); + QByteArray peer = r->s->peer(); + if(peer.isEmpty()) + continue; + + if(!packets.contains(peer)) + { + WsControlPacket packet; + packet.from = identity; + packets.insert(peer, packet); + } + + WsControlPacket &packet = packets[peer]; + WsControlPacket::Item i; i.cid = r->s->cid(); i.type = WsControlPacket::Item::KeepAlive; @@ -298,7 +358,7 @@ // if we're at max, send out now if(packet.items.count() >= PACKET_ITEMS_MAX) { - write(packet); + writeStream(packet, peer); packet.items.clear(); } } @@ -318,6 +378,19 @@ r->lastRefresh = now; sessionsByLastRefresh.insert(QPair(r->lastRefresh, r), r); + QByteArray peer = r->s->peer(); + if(peer.isEmpty()) + continue; + + if(!packets.contains(peer)) + { + WsControlPacket packet; + packet.from = identity; + packets.insert(peer, packet); + } + + WsControlPacket &packet = packets[peer]; + WsControlPacket::Item i; i.cid = r->s->cid(); i.type = WsControlPacket::Item::KeepAlive; @@ -327,14 +400,22 @@ // if we're at max, send out now if(packet.items.count() >= PACKET_ITEMS_MAX) { - write(packet); + writeStream(packet, peer); packet.items.clear(); } } // send the rest - if(!packet.items.isEmpty()) - write(packet); + QHashIterator it(packets); + while(it.hasNext()) + { + it.next(); + const QByteArray &peer = it.key(); + const WsControlPacket &packet = it.value(); + + if(!packet.items.isEmpty()) + writeStream(packet, peer); + } ++currentSessionRefreshBucket; if(currentSessionRefreshBucket >= SESSION_REFRESH_BUCKETS) @@ -342,15 +423,16 @@ } }; -WsControlManager::WsControlManager(QObject *parent) : - QObject(parent) +WsControlManager::WsControlManager() { - d = new Private(this); + d = std::make_unique(this); } -WsControlManager::~WsControlManager() +WsControlManager::~WsControlManager() = default; + +void WsControlManager::setIdentity(const QByteArray &id) { - delete d; + d->identity = id; } void WsControlManager::setIpcFileMode(int mode) @@ -358,16 +440,16 @@ d->ipcFileMode = mode; } -bool WsControlManager::setInSpec(const QString &spec) +bool WsControlManager::setInitSpecs(const QStringList &specs) { - d->inSpec = spec; - return d->setupIn(); + d->initSpecs = specs; + return d->setupInit(); } -bool WsControlManager::setOutSpec(const QString &spec) +bool WsControlManager::setStreamSpecs(const QStringList &specs) { - d->outSpec = spec; - return d->setupOut(); + d->streamSpecs = specs; + return d->setupStream(); } WsControlSession *WsControlManager::createSession(const QByteArray &cid) @@ -387,16 +469,14 @@ d->sessionsByCid.remove(cid); } -bool WsControlManager::canWriteImmediately() const +void WsControlManager::writeInit(const WsControlPacket::Item &item) { - assert(d->outSock); - - return d->outSock->canWriteImmediately(); + d->writeInit(item); } -void WsControlManager::write(const WsControlPacket::Item &item) +void WsControlManager::writeStream(const WsControlPacket::Item &item, const QByteArray &instanceAddress) { - d->write(item); + d->writeStream(item, instanceAddress); } void WsControlManager::registerKeepAlive(WsControlSession *s) diff -Nru pushpin-1.38.0/src/cpp/proxy/wscontrolmanager.h pushpin-1.39.1/src/cpp/proxy/wscontrolmanager.h --- pushpin-1.38.0/src/cpp/proxy/wscontrolmanager.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/wscontrolmanager.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,6 +24,7 @@ #ifndef WSCONTROLMANAGER_H #define WSCONTROLMANAGER_H +#include #include #include "packet/wscontrolpacket.h" @@ -33,25 +35,26 @@ Q_OBJECT public: - WsControlManager(QObject *parent = 0); + WsControlManager(); ~WsControlManager(); + void setIdentity(const QByteArray &id); void setIpcFileMode(int mode); - bool setInSpec(const QString &spec); - bool setOutSpec(const QString &spec); + bool setInitSpecs(const QStringList &specs); + bool setStreamSpecs(const QStringList &specs); WsControlSession *createSession(const QByteArray &cid); private: class Private; - Private *d; + std::unique_ptr d; friend class WsControlSession; void link(WsControlSession *s, const QByteArray &cid); void unlink(const QByteArray &cid); - bool canWriteImmediately() const; - void write(const WsControlPacket::Item &item); + void writeInit(const WsControlPacket::Item &item); + void writeStream(const WsControlPacket::Item &item, const QByteArray &instanceAddress); void registerKeepAlive(WsControlSession *s); void unregisterKeepAlive(WsControlSession *s); }; diff -Nru pushpin-1.38.0/src/cpp/proxy/wscontrolsession.cpp pushpin-1.39.1/src/cpp/proxy/wscontrolsession.cpp --- pushpin-1.38.0/src/cpp/proxy/wscontrolsession.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/wscontrolsession.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -39,9 +40,11 @@ WsControlSession *q; WsControlManager *manager; int nextReqId; + QList pendingItems; QHash pendingRequests; QList pendingSendEventWrites; QTimer *requestTimer; + QByteArray peer; QByteArray cid; QByteArray route; bool separateStats; @@ -88,14 +91,17 @@ { manager->registerKeepAlive(q); + int reqId = nextReqId++; + WsControlPacket::Item i; i.type = WsControlPacket::Item::Here; + i.requestId = QByteArray::number(reqId); i.route = route; i.separateStats = separateStats; i.channelPrefix = channelPrefix; i.uri = uri; i.ttl = SESSION_TTL; - write(i); + write(i, true); } void setupRequestTimer() @@ -161,15 +167,46 @@ write(i); } - void write(const WsControlPacket::Item &item) + void write(const WsControlPacket::Item &item, bool init = false) { - WsControlPacket::Item out = item; - out.cid = cid; - manager->write(out); + if(init) + { + WsControlPacket::Item out = item; + out.cid = cid; + + manager->writeInit(out); + } + else + { + if(!peer.isEmpty()) + { + WsControlPacket::Item out = item; + out.cid = cid; + + manager->writeStream(out, peer); + } + else + pendingItems += item; + } + } + + void flushPending() + { + while(!pendingItems.isEmpty()) + { + WsControlPacket::Item out = pendingItems.takeFirst(); + out.cid = cid; + + manager->writeStream(out, peer); + } } - void handle(const WsControlPacket::Item &item) + void handle(const QByteArray &from, const WsControlPacket::Item &item) { + peer = from; + + flushPending(); + if(item.type != WsControlPacket::Item::Ack && !item.requestId.isEmpty()) { // ack non-sends immediately @@ -201,7 +238,7 @@ else pendingSendEventWrites += QByteArray(); // placeholder - emit q->sendEventReceived(type, item.message, item.queue); + q->sendEventReceived(type, item.message, item.queue); } else if(item.type == WsControlPacket::Item::KeepAliveSetup) { @@ -212,22 +249,26 @@ mode = WsControl::Interval; else // idle mode = WsControl::Idle; - emit q->keepAliveSetupEventReceived(mode, item.timeout); + q->keepAliveSetupEventReceived(mode, item.timeout); } else - emit q->keepAliveSetupEventReceived(WsControl::NoKeepAlive); + q->keepAliveSetupEventReceived(WsControl::NoKeepAlive, -1); + } + else if(item.type == WsControlPacket::Item::Refresh) + { + q->refreshEventReceived(); } else if(item.type == WsControlPacket::Item::Close) { - emit q->closeEventReceived(item.code, item.reason); + q->closeEventReceived(item.code, item.reason); } else if(item.type == WsControlPacket::Item::Detach) { - emit q->detachEventReceived(); + q->detachEventReceived(); } else if(item.type == WsControlPacket::Item::Cancel) { - emit q->cancelEventReceived(); + q->cancelEventReceived(); } else if(item.type == WsControlPacket::Item::Ack) { @@ -261,19 +302,20 @@ pendingRequests.clear(); setupRequestTimer(); - emit q->error(); + q->error(); } }; -WsControlSession::WsControlSession(QObject *parent) : - QObject(parent) +WsControlSession::WsControlSession() { - d = new Private(this); + d = std::make_unique(this); } -WsControlSession::~WsControlSession() +WsControlSession::~WsControlSession() = default; + +QByteArray WsControlSession::peer() const { - delete d; + return d->peer; } QByteArray WsControlSession::cid() const @@ -317,11 +359,11 @@ d->sendEventWritten(); } -void WsControlSession::handle(const WsControlPacket::Item &item) +void WsControlSession::handle(const QByteArray &from, const WsControlPacket::Item &item) { assert(d->manager); - d->handle(item); + d->handle(from, item); } #include "wscontrolsession.moc" diff -Nru pushpin-1.38.0/src/cpp/proxy/wscontrolsession.h pushpin-1.39.1/src/cpp/proxy/wscontrolsession.h --- pushpin-1.38.0/src/cpp/proxy/wscontrolsession.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/wscontrolsession.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -28,6 +29,9 @@ #include "websocket.h" #include "wscontrol.h" #include "packet/wscontrolpacket.h" +#include + +using Signal = boost::signals2::signal; class WsControlManager; @@ -38,6 +42,7 @@ public: ~WsControlSession(); + QByteArray peer() const; QByteArray cid() const; void start(const QByteArray &routeId, bool separateStats, const QByteArray &channelPrefix, const QUrl &uri); @@ -48,23 +53,23 @@ // tell session that a received sendEvent has been written void sendEventWritten(); -signals: - void sendEventReceived(WebSocket::Frame::Type type, const QByteArray &message, bool queue); - void keepAliveSetupEventReceived(WsControl::KeepAliveMode mode, int timeout = -1); - void closeEventReceived(int code, const QByteArray &reason); // -1 for no code - void detachEventReceived(); - void cancelEventReceived(); - void error(); + boost::signals2::signal sendEventReceived; + boost::signals2::signal keepAliveSetupEventReceived; + Signal refreshEventReceived; + boost::signals2::signal closeEventReceived; // Use -1 for no code + Signal detachEventReceived; + Signal cancelEventReceived; + Signal error; private: class Private; friend class Private; - Private *d; + std::unique_ptr d; friend class WsControlManager; - WsControlSession(QObject *parent = 0); + WsControlSession(); void setup(WsControlManager *manager, const QByteArray &cid); - void handle(const WsControlPacket::Item &item); + void handle(const QByteArray &from, const WsControlPacket::Item &item); }; #endif diff -Nru pushpin-1.38.0/src/cpp/proxy/wsproxysession.cpp pushpin-1.39.1/src/cpp/proxy/wsproxysession.cpp --- pushpin-1.38.0/src/cpp/proxy/wsproxysession.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/wsproxysession.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2023 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -232,6 +233,34 @@ typedef QPair QueuedFrame; + struct WSConnections { + Connection connectedConnection; + Connection readyReadConnection; + Connection writeBytesChangedConnection; + Connection peerClosedConnection; + Connection closedConnection; + Connection errorConnection; + }; + + struct InWSConnections { + Connection readyReadConnection; + Connection framesWrittenConnection; + Connection writeBytesChangedConnection; + Connection peerClosedConnection; + Connection closedConnection; + Connection errorConnection; + }; + + struct WSProxyConnections { + Connection sendEventReceivedConnection; + Connection keepAliveSetupEventReceivedConnection; + Connection refreshEventReceivedConnection; + Connection closeEventReceivedConnection; + Connection detachEventReceivedConnection; + Connection cancelEventReceivedConnection; + Connection errorConnection; + }; + WsProxySession *q; State state; ZRoutes *zroutes; @@ -279,8 +308,11 @@ QList queuedInFrames; // frames to deliver after out read finishes LogUtil::Config logConfig; Callback> finishedByPassthroughCallback; - Connection keepAliveConneciton; + Connection keepAliveConnection; Connection aboutToSendRequestConnection; + map wsProxyConnectionMap; + WSConnections outWSConnection; + InWSConnections inWSConnection; Private(WsProxySession *_q, ZRoutes *_zroutes, ConnectionManager *_connectionManager, const LogUtil::Config &_logConfig, StatsManager *_statsManager, WsControlManager *_wsControlManager) : QObject(_q), @@ -321,10 +353,12 @@ cleanupKeepAliveTimer(); cleanupInSock(); - + + outWSConnection = WSConnections(); delete outSock; outSock = 0; + wsProxyConnectionMap.erase(wsControl); delete wsControl; wsControl = 0; @@ -340,6 +374,7 @@ if(inSock) { connectionManager->removeConnection(inSock); + inWSConnection = InWSConnections(); delete inSock; inSock = 0; } @@ -349,7 +384,7 @@ { if(keepAliveTimer) { - keepAliveConneciton.disconnect(); + keepAliveConnection.disconnect(); keepAliveTimer->setParent(0); keepAliveTimer->deleteLater(); keepAliveTimer = 0; @@ -369,12 +404,14 @@ inSock = sock; inSock->setParent(this); - connect(inSock, &WebSocket::readyRead, this, &Private::in_readyRead); - connect(inSock, &WebSocket::framesWritten, this, &Private::in_framesWritten); - connect(inSock, &WebSocket::writeBytesChanged, this, &Private::in_writeBytesChanged); - connect(inSock, &WebSocket::peerClosed, this, &Private::in_peerClosed); - connect(inSock, &WebSocket::closed, this, &Private::in_closed); - connect(inSock, &WebSocket::error, this, &Private::in_error); + inWSConnection = InWSConnections{ + inSock->readyRead.connect(boost::bind(&Private::in_readyRead, this)), + inSock->framesWritten.connect(boost::bind(&Private::in_framesWritten, this, boost::placeholders::_1, boost::placeholders::_2)), + inSock->writeBytesChanged.connect(boost::bind(&Private::in_writeBytesChanged, this)), + inSock->peerClosed.connect(boost::bind(&Private::in_peerClosed, this)), + inSock->closed.connect(boost::bind(&Private::in_closed, this)), + inSock->error.connect(boost::bind(&Private::in_error, this)) + }; requestData.uri = inSock->requestUri(); requestData.headers = inSock->requestHeaders(); @@ -547,13 +584,14 @@ outSock->setParent(this); } } - - connect(outSock, &WebSocket::connected, this, &Private::out_connected); - connect(outSock, &WebSocket::readyRead, this, &Private::out_readyRead); - connect(outSock, &WebSocket::writeBytesChanged, this, &Private::out_writeBytesChanged); - connect(outSock, &WebSocket::peerClosed, this, &Private::out_peerClosed); - connect(outSock, &WebSocket::closed, this, &Private::out_closed); - connect(outSock, &WebSocket::error, this, &Private::out_error); + outWSConnection = { + outSock->connected.connect(boost::bind(&Private::out_connected, this)), + outSock->readyRead.connect(boost::bind(&Private::out_readyRead, this)), + outSock->writeBytesChanged.connect(boost::bind(&Private::out_writeBytesChanged, this)), + outSock->peerClosed.connect(boost::bind(&Private::out_peerClosed, this)), + outSock->closed.connect(boost::bind(&Private::out_closed, this)), + outSock->error.connect(boost::bind(&Private::out_error, this)) + }; if(target.trusted) outSock->setIgnorePolicies(true); @@ -824,6 +862,7 @@ { if(outSock->state() == WebSocket::Connecting) { + outWSConnection = WSConnections(); delete outSock; outSock = 0; @@ -855,6 +894,7 @@ if(!detached) { + outWSConnection = WSConnections(); delete outSock; outSock = 0; } @@ -900,12 +940,15 @@ if(wsControlManager) { wsControl = wsControlManager->createSession(publicCid); - connect(wsControl, &WsControlSession::sendEventReceived, this, &Private::wsControl_sendEventReceived); - connect(wsControl, &WsControlSession::keepAliveSetupEventReceived, this, &Private::wsControl_keepAliveSetupEventReceived); - connect(wsControl, &WsControlSession::closeEventReceived, this, &Private::wsControl_closeEventReceived); - connect(wsControl, &WsControlSession::detachEventReceived, this, &Private::wsControl_detachEventReceived); - connect(wsControl, &WsControlSession::cancelEventReceived, this, &Private::wsControl_cancelEventReceived); - connect(wsControl, &WsControlSession::error, this, &Private::wsControl_error); + wsProxyConnectionMap[wsControl] = { + wsControl->sendEventReceived.connect(boost::bind(&Private::wsControl_sendEventReceived, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3)), + wsControl->keepAliveSetupEventReceived.connect(boost::bind(&Private::wsControl_keepAliveSetupEventReceived, this, boost::placeholders::_1, boost::placeholders::_2)), + wsControl->refreshEventReceived.connect(boost::bind(&Private::wsControl_refreshEventReceived, this)), + wsControl->closeEventReceived.connect(boost::bind(&Private::wsControl_closeEventReceived, this, boost::placeholders::_1, boost::placeholders::_2)), + wsControl->detachEventReceived.connect(boost::bind(&Private::wsControl_detachEventReceived, this)), + wsControl->cancelEventReceived.connect(boost::bind(&Private::wsControl_cancelEventReceived, this)), + wsControl->error.connect(boost::bind(&Private::wsControl_error, this)) + }; wsControl->start(route.id, route.separateStats, channelPrefix, inSock->requestUri()); foreach(const QString &subChannel, target.subscriptions) @@ -948,6 +991,7 @@ { int code = outSock->peerCloseCode(); QString reason = outSock->peerCloseReason(); + outWSConnection = WSConnections(); delete outSock; outSock = 0; @@ -964,6 +1008,7 @@ if(detached) { + outWSConnection = WSConnections(); delete outSock; outSock = 0; @@ -990,6 +1035,7 @@ break; } + outWSConnection = WSConnections(); delete outSock; outSock = 0; @@ -1000,6 +1046,7 @@ { cleanupInSock(); + outWSConnection = WSConnections(); delete outSock; outSock = 0; @@ -1014,6 +1061,7 @@ woh->setHeaders(requestData.headers); } +private: void wsControl_sendEventReceived(WebSocket::Frame::Type type, const QByteArray &message, bool queue) { // this method accepts a full message, which must be typed @@ -1061,8 +1109,8 @@ if(!keepAliveTimer) { - keepAliveTimer = new RTimer(this); - keepAliveConneciton = keepAliveTimer->timeout.connect(boost::bind(&Private::keepAliveTimer_timeout, this)); + keepAliveTimer = new RTimer; + keepAliveConnection = keepAliveTimer->timeout.connect(boost::bind(&Private::keepAliveTimer_timeout, this)); keepAliveTimer->setSingleShot(true); } @@ -1074,6 +1122,13 @@ } } + void wsControl_refreshEventReceived() + { + WebSocketOverHttp *woh = qobject_cast(outSock); + if(woh) + woh->refresh(); + } + void wsControl_closeEventReceived(int code, const QByteArray &reason) { if(!detached && outSock && outSock->state() != WebSocket::Closing) @@ -1099,6 +1154,7 @@ { if(outSock) { + outWSConnection = WSConnections(); delete outSock; outSock = 0; } @@ -1114,7 +1170,6 @@ wsControl_cancelEventReceived(); } -private: void keepAliveTimer_timeout() { wsControl->sendNeedKeepAlive(); diff -Nru pushpin-1.38.0/src/cpp/proxy/wsproxysession.h pushpin-1.39.1/src/cpp/proxy/wsproxysession.h --- pushpin-1.38.0/src/cpp/proxy/wsproxysession.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/wsproxysession.h 2024-03-18 18:42:24.000000000 +0000 @@ -27,9 +27,9 @@ #include "callback.h" #include "logutil.h" #include "domainmap.h" - #include +using std::map; using Connection = boost::signals2::scoped_connection; namespace Jwt { diff -Nru pushpin-1.38.0/src/cpp/proxy/zrpcchecker.cpp pushpin-1.39.1/src/cpp/proxy/zrpcchecker.cpp --- pushpin-1.38.0/src/cpp/proxy/zrpcchecker.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/proxy/zrpcchecker.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -29,12 +29,12 @@ #define CHECK_TIMEOUT 8 +using std::map; + class ZrpcChecker::Private : public QObject { Q_OBJECT - Connection finishedConnection; - public: class Item { @@ -55,10 +55,16 @@ } }; + struct ZrpcReqConnections{ + Connection finishedConnection; + Connection destroyedConnection; + }; + ZrpcChecker *q; bool avail; QTimer *timer; QHash requestsByReq; + map reqConnectionMap; Private(ZrpcChecker *_q) : QObject(_q), @@ -90,7 +96,7 @@ { it.next(); Item *i = it.value(); - i->req->disconnect(this); + reqConnectionMap.erase(i->req); delete i; } @@ -108,8 +114,10 @@ if(i) return; // already watching - finishedConnection = req->finished.connect(boost::bind(&Private::req_finished, this, req)); - connect(req, &ZrpcRequest::destroyed, this, &Private::req_destroyed); + reqConnectionMap[req] = { + req->finished.connect(boost::bind(&Private::req_finished, this, req)), + req->destroyed.connect(boost::bind(&Private::req_destroyed, this, req)) + }; i = new Item; i->req = req; @@ -135,6 +143,7 @@ // if we aren't watching (or were watching, but no // longer watching), then just delete what we were // given + reqConnectionMap.erase(req); delete req; } } @@ -172,7 +181,7 @@ bool success = req->success(); ZrpcRequest::ErrorCondition e = req->errorCondition(); - req->disconnect(this); + reqConnectionMap.erase(req); requestsByReq.remove(req); delete i; @@ -194,16 +203,17 @@ } } -public slots: - void req_destroyed(QObject *obj) + void req_destroyed(ZrpcRequest *req) { - Item *i = requestsByReq.value((ZrpcRequest *)obj); + Item *i = requestsByReq.value(req); assert(i); - requestsByReq.remove((ZrpcRequest *)obj); + reqConnectionMap.erase(req); + requestsByReq.remove(req); delete i; } +public slots: void timer_timeout() { avail = false; diff -Nru pushpin-1.38.0/src/cpp/qtcompat.h pushpin-1.39.1/src/cpp/qtcompat.h --- pushpin-1.38.0/src/cpp/qtcompat.h 1970-01-01 00:00:00.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qtcompat.h 2024-03-18 18:42:24.000000000 +0000 @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 Fastly, Inc. + * + * This file is part of Pushpin. + * + * $FANOUT_BEGIN_LICENSE:APACHE2$ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $FANOUT_END_LICENSE$ + */ + +#include +#include + +inline QMetaType::Type typeId(const QVariant &v) +{ +#if QT_VERSION >= 0x060000 + return (QMetaType::Type)v.typeId(); +#else + return (QMetaType::Type)v.type(); +#endif +} + +inline bool canConvert(const QVariant &v, QMetaType::Type type) +{ +#if QT_VERSION >= 0x060000 + return v.canConvert(QMetaType(type)); +#else + return v.canConvert(type); +#endif +} diff -Nru pushpin-1.38.0/src/cpp/qzmq/examples/helloclient/helloclient.cpp pushpin-1.39.1/src/cpp/qzmq/examples/helloclient/helloclient.cpp --- pushpin-1.38.0/src/cpp/qzmq/examples/helloclient/helloclient.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qzmq/examples/helloclient/helloclient.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -2,6 +2,9 @@ #include #include #include "qzmqsocket.h" +#include + +using Connection = boost::signals2::scoped_connection; class App : public QObject { @@ -9,6 +12,8 @@ private: QZmq::Socket sock; + Connection rrConnection; + Connection mwConnection; public: App() : @@ -16,21 +21,11 @@ { } -public slots: - void start() + void sock_messagesWritten(int count) { - connect(&sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); - connect(&sock, SIGNAL(messagesWritten(int)), SLOT(sock_messagesWritten(int))); - sock.connectToAddress("tcp://localhost:5555"); - QByteArray out = "hello"; - printf("writing: %s\n", out.data()); - sock.write(QList() << out); + printf("messages written: %d\n", count); } -signals: - void quit(); - -private slots: void sock_readyRead() { QList resp = sock.read(); @@ -38,10 +33,19 @@ emit quit(); } - void sock_messagesWritten(int count) +public slots: + void start() { - printf("messages written: %d\n", count); + rrConnection = sock.readyRead.connect(boost::bind(&Private::sock_readyRead, this)); + mwConnection = sock.messagesWritten.connect(boost::bind(&Private::sock_messagesWritten, this, boost::placeholders::_1)); + sock.connectToAddress("tcp://localhost:5555"); + QByteArray out = "hello"; + printf("writing: %s\n", out.data()); + sock.write(QList() << out); } + +signals: + void quit(); }; int main(int argc, char **argv) diff -Nru pushpin-1.38.0/src/cpp/qzmq/examples/helloserver/helloserver.cpp pushpin-1.39.1/src/cpp/qzmq/examples/helloserver/helloserver.cpp --- pushpin-1.38.0/src/cpp/qzmq/examples/helloserver/helloserver.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qzmq/examples/helloserver/helloserver.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -11,18 +11,11 @@ private: QZmq::RepRouter sock; -public slots: - void start() + void sock_messagesWritten(int count) { - connect(&sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); - connect(&sock, SIGNAL(messagesWritten(int)), SLOT(sock_messagesWritten(int))); - sock.bind("tcp://*:5555"); + printf("messages written: %d\n", count); } -signals: - void quit(); - -private slots: void sock_readyRead() { QZmq::ReqMessage msg = sock.read(); @@ -38,10 +31,16 @@ sock.write(msg.createReply(QList() << out)); } - void sock_messagesWritten(int count) +public slots: + void start() { - printf("messages written: %d\n", count); + rrConnection = sock.readyRead.connect(boost::bind(&Private::sock_readyRead, this)); + mwConnection = sock.messagesWritten.connect(boost::bind(&Private::sock_messagesWritten, this, boost::placeholders::_1)); + sock.bind("tcp://*:5555"); } + +signals: + void quit(); }; int main(int argc, char **argv) diff -Nru pushpin-1.38.0/src/cpp/qzmq/src/qzmqreprouter.cpp pushpin-1.39.1/src/cpp/qzmq/src/qzmqreprouter.cpp --- pushpin-1.38.0/src/cpp/qzmq/src/qzmqreprouter.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qzmq/src/qzmqreprouter.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -28,45 +28,39 @@ namespace QZmq { -class RepRouter::Private : public QObject +class RepRouter::Private { - Q_OBJECT - public: RepRouter *q; - Socket *sock; + std::unique_ptr sock; + Connection mWConnection; + Connection rrConnection; Private(RepRouter *_q) : - QObject(_q), q(_q) { - sock = new Socket(Socket::Router, this); - connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); - connect(sock, SIGNAL(messagesWritten(int)), SLOT(sock_messagesWritten(int))); + sock = std::make_unique(Socket::Router); + rrConnection = sock->readyRead.connect(boost::bind(&Private::sock_readyRead, this)); + mWConnection = sock->messagesWritten.connect(boost::bind(&Private::sock_messagesWritten, this, boost::placeholders::_1)); } -public slots: - void sock_readyRead() + void sock_messagesWritten(int count) { - emit q->readyRead(); + q->messagesWritten(count); } - void sock_messagesWritten(int count) + void sock_readyRead() { - emit q->messagesWritten(count); + q->readyRead(); } }; -RepRouter::RepRouter(QObject *parent) : - QObject(parent) +RepRouter::RepRouter() { - d = new Private(this); + d = std::make_unique(this); } -RepRouter::~RepRouter() -{ - delete d; -} +RepRouter::~RepRouter() = default; void RepRouter::setShutdownWaitTime(int msecs) { @@ -100,4 +94,3 @@ } -#include "qzmqreprouter.moc" diff -Nru pushpin-1.38.0/src/cpp/qzmq/src/qzmqreprouter.h pushpin-1.39.1/src/cpp/qzmq/src/qzmqreprouter.h --- pushpin-1.38.0/src/cpp/qzmq/src/qzmqreprouter.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qzmq/src/qzmqreprouter.h 2024-03-18 18:42:24.000000000 +0000 @@ -25,17 +25,20 @@ #define QZMQREPROUTER_H #include +#include + +using Signal = boost::signals2::signal; +using SignalInt = boost::signals2::signal; +using Connection = boost::signals2::scoped_connection; namespace QZmq { class ReqMessage; -class RepRouter : public QObject +class RepRouter { - Q_OBJECT - public: - RepRouter(QObject *parent = 0); + RepRouter(); ~RepRouter(); void setShutdownWaitTime(int msecs); @@ -48,16 +51,15 @@ ReqMessage read(); void write(const ReqMessage &message); -signals: - void readyRead(); - void messagesWritten(int count); + Signal readyRead; + SignalInt messagesWritten; private: Q_DISABLE_COPY(RepRouter) class Private; friend class Private; - Private *d; + std::unique_ptr d; }; } diff -Nru pushpin-1.38.0/src/cpp/qzmq/src/qzmqsocket.cpp pushpin-1.39.1/src/cpp/qzmq/src/qzmqsocket.cpp --- pushpin-1.38.0/src/cpp/qzmq/src/qzmqsocket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qzmq/src/qzmqsocket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2020 Justin Karneges + * Copyright (C) 2024 Fastly, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the @@ -92,6 +93,14 @@ assert(ret == 0); } +static void set_router_mandatory(void *sock, bool on) +{ + int v = on ? 1 : 0; + size_t opt_len = sizeof(v); + int ret = wzmq_setsockopt(sock, WZMQ_ROUTER_MANDATORY, &v, opt_len); + assert(ret == 0); +} + #else static void set_immediate(void *sock, bool on) @@ -407,7 +416,7 @@ assert(sock != NULL); sn_read = new QSocketNotifier(get_fd(sock), QSocketNotifier::Read, this); - connect(sn_read, SIGNAL(activated(int)), SLOT(sn_read_activated())); + connect(sn_read, &QSocketNotifier::activated, this, &Private::sn_read_activated); sn_read->setEnabled(true); updateTimer = new QTimer(this); @@ -587,7 +596,7 @@ if(canRead) { QPointer self = this; - emit q->readyRead(); + q->readyRead(); if(!self) return; } @@ -597,7 +606,7 @@ int count = pendingWritten; pendingWritten = 0; - emit q->messagesWritten(count); + q->messagesWritten(count); } } @@ -708,6 +717,11 @@ set_immediate(d->sock, on); } +void Socket::setRouterMandatoryEnabled(bool on) +{ + set_router_mandatory(d->sock, on); +} + void Socket::setTcpKeepAliveEnabled(bool on) { set_tcp_keepalive(d->sock, on ? 1 : 0); diff -Nru pushpin-1.38.0/src/cpp/qzmq/src/qzmqsocket.h pushpin-1.39.1/src/cpp/qzmq/src/qzmqsocket.h --- pushpin-1.38.0/src/cpp/qzmq/src/qzmqsocket.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qzmq/src/qzmqsocket.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2015 Justin Karneges + * Copyright (C) 2024 Fastly, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the @@ -25,6 +26,11 @@ #define QZMQSOCKET_H #include +#include + +using Signal = boost::signals2::signal; +using SignalInt = boost::signals2::signal; +using Connection = boost::signals2::scoped_connection; namespace QZmq { @@ -80,6 +86,7 @@ void setReceiveHwm(int hwm); void setImmediateEnabled(bool on); + void setRouterMandatoryEnabled(bool on); void setTcpKeepAliveEnabled(bool on); void setTcpKeepAliveParameters(int idle = -1, int count = -1, int interval = -1); @@ -98,9 +105,8 @@ QList read(); void write(const QList &message); -signals: - void readyRead(); - void messagesWritten(int count); + Signal readyRead; + SignalInt messagesWritten; private: Q_DISABLE_COPY(Socket) diff -Nru pushpin-1.38.0/src/cpp/qzmq/src/qzmqvalve.cpp pushpin-1.39.1/src/cpp/qzmq/src/qzmqvalve.cpp --- pushpin-1.38.0/src/cpp/qzmq/src/qzmqvalve.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qzmq/src/qzmqvalve.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -38,6 +38,7 @@ bool isOpen; bool pendingRead; int maxReadsPerEvent; + boost::signals2::scoped_connection rrConnection; Private(Valve *_q) : QObject(_q), @@ -52,7 +53,7 @@ void setup(QZmq::Socket *_sock) { sock = _sock; - connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); + rrConnection = sock->readyRead.connect(boost::bind(&Private::sock_readyRead, this)); } void queueRead() @@ -81,7 +82,7 @@ if(!msg.isEmpty()) { - emit q->readyRead(msg); + q->readyRead(msg); if(!self) return; } @@ -90,7 +91,6 @@ } } -private slots: void sock_readyRead() { if(pendingRead) @@ -99,6 +99,7 @@ tryRead(); } +private slots: void queuedRead() { pendingRead = false; diff -Nru pushpin-1.38.0/src/cpp/qzmq/src/qzmqvalve.h pushpin-1.39.1/src/cpp/qzmq/src/qzmqvalve.h --- pushpin-1.38.0/src/cpp/qzmq/src/qzmqvalve.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/qzmq/src/qzmqvalve.h 2024-03-18 18:42:24.000000000 +0000 @@ -25,6 +25,10 @@ #define QZMQVALVE_H #include +#include + +using SignalList = boost::signals2::signal&)>; +using Connection = boost::signals2::scoped_connection; namespace QZmq { @@ -45,8 +49,7 @@ void open(); void close(); -signals: - void readyRead(const QList &message); + SignalList readyRead; private: class Private; diff -Nru pushpin-1.38.0/src/cpp/rtimer.cpp pushpin-1.39.1/src/cpp/rtimer.cpp --- pushpin-1.38.0/src/cpp/rtimer.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/rtimer.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2021 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -170,10 +171,9 @@ } } -static TimerManager *g_manager = 0; +static thread_local TimerManager *g_manager = 0; -RTimer::RTimer(QObject *parent) : - QObject(parent), +RTimer::RTimer() : singleShot_(false), interval_(0), timerId_(-1) diff -Nru pushpin-1.38.0/src/cpp/rtimer.h pushpin-1.39.1/src/cpp/rtimer.h --- pushpin-1.38.0/src/cpp/rtimer.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/rtimer.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2021 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -35,7 +36,7 @@ Q_OBJECT public: - RTimer(QObject *parent = 0); + RTimer(); ~RTimer(); bool isActive() const; @@ -45,6 +46,7 @@ void start(); void stop(); + // initialization is thread local static void init(int capacity); // only call if there are no active RTimers diff -Nru pushpin-1.38.0/src/cpp/runner/condureservice.cpp pushpin-1.39.1/src/cpp/runner/condureservice.cpp --- pushpin-1.38.0/src/cpp/runner/condureservice.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/condureservice.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -42,9 +42,7 @@ int maxconn, bool allowCompression, const QList &ports, - bool enableClient, - QObject *parent) : - Service(parent) + bool enableClient) { args_ += binFile; diff -Nru pushpin-1.38.0/src/cpp/runner/condureservice.h pushpin-1.39.1/src/cpp/runner/condureservice.h --- pushpin-1.38.0/src/cpp/runner/condureservice.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/condureservice.h 2024-03-18 18:42:24.000000000 +0000 @@ -28,8 +28,6 @@ class CondureService : public Service { - Q_OBJECT - public: CondureService( const QString &name, @@ -44,8 +42,7 @@ int maxconn, bool allowCompression, const QList &ports, - bool enableClient, - QObject *parent = 0); + bool enableClient); static bool hasClientMode(const QString &binFile); diff -Nru pushpin-1.38.0/src/cpp/runner/m2adapterservice.cpp pushpin-1.39.1/src/cpp/runner/m2adapterservice.cpp --- pushpin-1.38.0/src/cpp/runner/m2adapterservice.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/m2adapterservice.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -36,9 +36,7 @@ const QString &ipcPrefix, const QString &filePrefix, int logLevel, - const QList &ports, - QObject *parent) : - Service(parent) + const QList &ports) { args_ += binFile; args_ += "--config=" + QDir(runDir).filePath(filePrefix + "m2adapter.conf"); diff -Nru pushpin-1.38.0/src/cpp/runner/m2adapterservice.h pushpin-1.39.1/src/cpp/runner/m2adapterservice.h --- pushpin-1.38.0/src/cpp/runner/m2adapterservice.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/m2adapterservice.h 2024-03-18 18:42:24.000000000 +0000 @@ -27,8 +27,6 @@ class M2AdapterService : public Service { - Q_OBJECT - public: M2AdapterService( const QString &binFile, @@ -38,8 +36,7 @@ const QString &ipcPrefix, const QString &filePrefix, int logLevel, - const QList &ports, - QObject *parent = 0); + const QList &ports); // reimplemented diff -Nru pushpin-1.38.0/src/cpp/runner/mongrel2service.cpp pushpin-1.39.1/src/cpp/runner/mongrel2service.cpp --- pushpin-1.38.0/src/cpp/runner/mongrel2service.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/mongrel2service.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -40,9 +40,7 @@ const QString &filePrefix, int port, bool ssl, - int logLevel, - QObject *parent) : - Service(parent), + int logLevel) : logLevel_(logLevel) { args_ += binFile; @@ -124,6 +122,10 @@ return true; } +bool Mongrel2Service::alwaysLogStatus() const +{ + return true; +} QString Mongrel2Service::filterLogLine(const int level, const QString &line) const { diff -Nru pushpin-1.38.0/src/cpp/runner/mongrel2service.h pushpin-1.39.1/src/cpp/runner/mongrel2service.h --- pushpin-1.38.0/src/cpp/runner/mongrel2service.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/mongrel2service.h 2024-03-18 18:42:24.000000000 +0000 @@ -40,8 +40,7 @@ const QString &filePrefix, int port, bool ssl, - int logLevel, - QObject *parent = 0); + int logLevel); static bool generateConfigFile(const QString &m2shBinFile, const QString &configTemplateFile, const QString &runDir, const QString &logDir, const QString &ipcPrefix, const QString &filePrefix, const QString &certsDir, int clientBufferSize, int maxconn, const QList &ports, int logLevel); @@ -49,6 +48,7 @@ virtual QStringList arguments() const; virtual bool acceptSighup() const; + virtual bool alwaysLogStatus() const; virtual QString formatLogLine(const QString &line) const; private: diff -Nru pushpin-1.38.0/src/cpp/runner/pushpinhandlerservice.cpp pushpin-1.39.1/src/cpp/runner/pushpinhandlerservice.cpp --- pushpin-1.38.0/src/cpp/runner/pushpinhandlerservice.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/pushpinhandlerservice.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -33,9 +33,7 @@ const QString &ipcPrefix, const QString &filePrefix, int portOffset, - int logLevel, - QObject *parent) : - Service(parent) + int logLevel) { args_ += binFile; args_ += "--config=" + configFile; diff -Nru pushpin-1.38.0/src/cpp/runner/pushpinhandlerservice.h pushpin-1.39.1/src/cpp/runner/pushpinhandlerservice.h --- pushpin-1.38.0/src/cpp/runner/pushpinhandlerservice.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/pushpinhandlerservice.h 2024-03-18 18:42:24.000000000 +0000 @@ -27,8 +27,6 @@ class PushpinHandlerService : public Service { - Q_OBJECT - public: PushpinHandlerService( const QString &binFile, @@ -38,8 +36,7 @@ const QString &ipcPrefix, const QString &filePrefix, int portOffset, - int logLevel, - QObject *parent = 0); + int logLevel); // reimplemented diff -Nru pushpin-1.38.0/src/cpp/runner/pushpinproxyservice.cpp pushpin-1.39.1/src/cpp/runner/pushpinproxyservice.cpp --- pushpin-1.38.0/src/cpp/runner/pushpinproxyservice.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/pushpinproxyservice.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -34,9 +34,7 @@ const QString &filePrefix, int logLevel, const QStringList &routeLines, - bool quietCheck, - QObject *parent) : - Service(parent) + bool quietCheck) { args_ += binFile; args_ += "--config=" + configFile; diff -Nru pushpin-1.38.0/src/cpp/runner/pushpinproxyservice.h pushpin-1.39.1/src/cpp/runner/pushpinproxyservice.h --- pushpin-1.38.0/src/cpp/runner/pushpinproxyservice.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/pushpinproxyservice.h 2024-03-18 18:42:24.000000000 +0000 @@ -27,8 +27,6 @@ class PushpinProxyService : public Service { - Q_OBJECT - public: PushpinProxyService( const QString &binFile, @@ -39,8 +37,7 @@ const QString &filePrefix, int logLevel, const QStringList &routeLines, - bool quietCheck, - QObject *parent = 0); + bool quietCheck); // reimplemented diff -Nru pushpin-1.38.0/src/cpp/runner/runnerapp.cpp pushpin-1.39.1/src/cpp/runner/runnerapp.cpp --- pushpin-1.38.0/src/cpp/runner/runnerapp.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/runnerapp.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -41,6 +41,13 @@ #include "pushpinhandlerservice.h" #include "config.h" +struct ServiceConnections{ + Connection startedConnection; + Connection stoppedConnection; + Connection logConnection; + Connection errConnection; +}; + static void trimlist(QStringList *list) { for(int n = 0; n < list->count(); ++n) @@ -246,10 +253,8 @@ return CommandLineOk; } -class RunnerApp::Private : public QObject +class RunnerApp::Private { - Q_OBJECT - public: RunnerApp *q; ArgsData args; @@ -258,9 +263,9 @@ bool errored; Connection quitConnection; Connection hupConnection; + map serviceConnectionMap; Private(RunnerApp *_q) : - QObject(_q), q(_q), stopping(false), errored(false) @@ -584,7 +589,7 @@ if(!serviceNames.contains("zurl") && CondureService::hasClientMode(condureBin)) useClient = true; - services += new CondureService("condure", condureBin, runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, logLevels.value("condure", defaultLevel), certsDir, clientBufferSize, clientMaxConnections, allowCompression, ports, useClient, this); + services += new CondureService("condure", condureBin, runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, logLevels.value("condure", defaultLevel), certsDir, clientBufferSize, clientMaxConnections, allowCompression, ports, useClient); } if(serviceNames.contains("mongrel2")) @@ -605,7 +610,7 @@ } foreach(const ListenPort &p, ports) - services += new Mongrel2Service(m2Bin, QDir(runDir).filePath(QString("%1mongrel2.sqlite").arg(filePrefix)), "default_" + QString::number(p.port), runDir, !args.mergeOutput ? logDir : QString(), filePrefix, p.port, p.ssl, logLevels.value("mongrel2", defaultLevel), this); + services += new Mongrel2Service(m2Bin, QDir(runDir).filePath(QString("%1mongrel2.sqlite").arg(filePrefix)), "default_" + QString::number(p.port), runDir, !args.mergeOutput ? logDir : QString(), filePrefix, p.port, p.ssl, logLevels.value("mongrel2", defaultLevel)); } if(serviceNames.contains("m2adapter")) @@ -614,7 +619,7 @@ foreach(const ListenPort &p, ports) portsOnly += p.port; - services += new M2AdapterService(m2aBin, QDir(libDir).filePath("m2adapter.conf.template"), runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, logLevels.value("m2adapter", defaultLevel), portsOnly, this); + services += new M2AdapterService(m2aBin, QDir(libDir).filePath("m2adapter.conf.template"), runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, logLevels.value("m2adapter", defaultLevel), portsOnly); } bool quietCheck = false; @@ -625,26 +630,28 @@ if(settings.contains("runner/zurl_bin")) zurlBin = settings.value("runner/zurl_bin").toString(); - services += new ZurlService(zurlBin, QDir(libDir).filePath("zurl.conf.template"), runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, logLevels.value("zurl", defaultLevel), this); + services += new ZurlService(zurlBin, QDir(libDir).filePath("zurl.conf.template"), runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, logLevels.value("zurl", defaultLevel)); // when zurl is managed by pushpin, log updates checks as debug level quietCheck = true; } if(serviceNames.contains("pushpin-proxy")) - services += new PushpinProxyService(proxyBin, configFile, runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, logLevels.value("pushpin-proxy", defaultLevel), args.routeLines, quietCheck, this); + services += new PushpinProxyService(proxyBin, configFile, runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, logLevels.value("pushpin-proxy", defaultLevel), args.routeLines, quietCheck); if(serviceNames.contains("pushpin-handler")) - services += new PushpinHandlerService(handlerBin, configFile, runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, portOffset, logLevels.value("pushpin-handler", defaultLevel), this); + services += new PushpinHandlerService(handlerBin, configFile, runDir, !args.mergeOutput ? logDir : QString(), ipcPrefix, filePrefix, portOffset, logLevels.value("pushpin-handler", defaultLevel)); foreach(Service *s, services) { - connect(s, &Service::started, this, &Private::service_started); - connect(s, &Service::stopped, this, &Private::service_stopped); - connect(s, &Service::logLine, this, &Private::service_logLine); - connect(s, &Service::error, this, &Private::service_error); + serviceConnectionMap[s] = { + s->started.connect(boost::bind(&Private::service_started, this)), + s->stopped.connect(boost::bind(&Private::service_stopped, this, s)), + s->logLine.connect(boost::bind(&Private::service_logLine, this, boost::placeholders::_1, s)), + s->error.connect(boost::bind(&Private::service_error, this, boost::placeholders::_1, s)) + }; - if(!args.mergeOutput || qobject_cast(s)) + if(!args.mergeOutput || s->alwaysLogStatus()) log_info("starting %s", qPrintable(s->name())); s->start(); @@ -680,7 +687,7 @@ { foreach(Service *s, services) { - if(!args.mergeOutput || qobject_cast(s)) + if(!args.mergeOutput || s->alwaysLogStatus()) log_info("stopping %s", qPrintable(s->name())); s->stop(); @@ -701,7 +708,6 @@ q->quit(errored ? 1 : 0); } -private slots: void service_started() { bool allStarted = true; @@ -718,9 +724,9 @@ log_info("started"); } - void service_stopped() + void service_stopped(Service *s) { - Service *s = (Service *)sender(); + serviceConnectionMap.erase(s); services.removeAll(s); delete s; @@ -728,22 +734,20 @@ checkStopped(); } - void service_logLine(const QString &line) + void service_logLine(const QString &line, Service *s) { - Service *s = (Service *)sender(); - QString out = tryInsertPrefix(s->formatLogLine(line), '[' + s->name() + "] "); if(!out.isEmpty()) { log_raw(qPrintable(out)); } } - void service_error(const QString &error) + void service_error(const QString &error, Service *s) { - Service *s = (Service *)sender(); - log_error("%s: %s", qPrintable(s->name()), qPrintable(error)); + serviceConnectionMap.erase(s); + services.removeAll(s); delete s; @@ -761,7 +765,6 @@ } } -private: void reload() { log_info("reloading"); @@ -796,20 +799,14 @@ } }; -RunnerApp::RunnerApp(QObject *parent) : - QObject(parent) -{ - d = new Private(this); +RunnerApp::RunnerApp() { + d = std::make_unique(this); } -RunnerApp::~RunnerApp() -{ - delete d; -} +RunnerApp::~RunnerApp() = default; void RunnerApp::start() { d->start(); } -#include "runnerapp.moc" diff -Nru pushpin-1.38.0/src/cpp/runner/runnerapp.h pushpin-1.39.1/src/cpp/runner/runnerapp.h --- pushpin-1.38.0/src/cpp/runner/runnerapp.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/runnerapp.h 2024-03-18 18:42:24.000000000 +0000 @@ -23,18 +23,17 @@ #ifndef RUNNERAPP_H #define RUNNERAPP_H -#include #include +#include +using std::map; using SignalInt = boost::signals2::signal; using Connection = boost::signals2::scoped_connection; -class RunnerApp : public QObject +class RunnerApp { - Q_OBJECT - public: - RunnerApp(QObject *parent = 0); + RunnerApp(); ~RunnerApp(); void start(); @@ -44,7 +43,7 @@ private: class Private; friend class Private; - Private *d; + std::unique_ptr d; }; #endif diff -Nru pushpin-1.38.0/src/cpp/runner/service.cpp pushpin-1.39.1/src/cpp/runner/service.cpp --- pushpin-1.38.0/src/cpp/runner/service.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/service.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -202,6 +202,11 @@ } private slots: + void doError(const QString &str) + { + q->error(str); + } + void proc_started() { if(!pidFile.isEmpty()) @@ -211,7 +216,7 @@ } state = Started; - emit q->started(); + q->started(); if(terminateAfterStarted) doStop(); @@ -225,7 +230,7 @@ if(!line.isEmpty() && line[line.length() - 1] == '\n') line.truncate(line.length() - 1); - emit q->logLine(QString::fromLocal8Bit(line)); + q->logLine(QString::fromLocal8Bit(line)); } } @@ -238,7 +243,7 @@ { state = Stopped; cleanup(); - emit q->error("Exited unexpectedly"); + q->error("Exited unexpectedly"); return; } @@ -248,19 +253,19 @@ if(exitStatus == QProcess::CrashExit) { if(sentKill) - emit q->stopped(); + q->stopped(); else - emit q->error("Exited uncleanly"); + q->error("Exited uncleanly"); return; } if(exitCode != 0) { - emit q->error("Unexpected return code: " + QString::number(exitCode)); + q->error("Unexpected return code: " + QString::number(exitCode)); return; } - emit q->stopped(); + q->stopped(); } void proc_errorOccurred(QProcess::ProcessError error) @@ -271,7 +276,7 @@ state = Stopped; cleanup(); - emit q->error("Error running: " + program); + q->error("Error running: " + program); } else { @@ -312,6 +317,11 @@ return false; } +bool Service::alwaysLogStatus() const +{ + return false; +} + bool Service::isStarted() const { return (d->state != Private::NotStarted && d->state != Private::Starting); @@ -328,7 +338,7 @@ if(!preStart()) { QString str = "Failure preparing to start"; - QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, Q_ARG(QString, str)); + QMetaObject::invokeMethod(this, "doError", Qt::QueuedConnection, Q_ARG(QString, str)); return; } diff -Nru pushpin-1.38.0/src/cpp/runner/service.h pushpin-1.39.1/src/cpp/runner/service.h --- pushpin-1.38.0/src/cpp/runner/service.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/service.h 2024-03-18 18:42:24.000000000 +0000 @@ -25,6 +25,11 @@ #include #include +#include + +using Signal = boost::signals2::signal; +using SignalStr = boost::signals2::signal; +using Connection = boost::signals2::scoped_connection; class Service : public QObject { @@ -38,6 +43,7 @@ virtual QStringList arguments() const = 0; virtual bool acceptSighup() const; + virtual bool alwaysLogStatus() const; virtual bool isStarted() const; virtual bool preStart(); @@ -49,17 +55,16 @@ void sendSighup(); + Signal started; + Signal stopped; + SignalStr logLine; + SignalStr error; + protected: void setName(const QString &name); void setStandardOutputFile(const QString &file); void setPidFile(const QString &file); -signals: - void started(); - void stopped(); - void logLine(const QString &line); - void error(const QString &message); - private: class Private; Private *d; diff -Nru pushpin-1.38.0/src/cpp/runner/template.cpp pushpin-1.39.1/src/cpp/runner/template.cpp --- pushpin-1.38.0/src/cpp/runner/template.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/template.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-2020 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -29,6 +30,7 @@ #include #include +#include "qtcompat.h" #include "log.h" namespace Template { @@ -248,7 +250,7 @@ return QVariant(); QVariant subContext = context[parent]; - if(subContext.type() != QVariant::Map) + if(typeId(subContext) != QMetaType::QVariantMap) return QVariant(); return getVar(member, subContext.toMap()); @@ -284,11 +286,11 @@ else { QVariant val = getVar(s, context); - if(val.type() == QVariant::String) + if(typeId(val) == QMetaType::QString) return !val.toString().isEmpty(); - else if(val.type() == QVariant::Bool) + else if(typeId(val) == QMetaType::Bool) return val.toBool(); - else if(val.canConvert(QVariant::Int)) + else if(canConvert(val, QMetaType::Int)) return (val.toInt() != 0); else return false; @@ -310,7 +312,7 @@ QString containerName = s.mid(at + 4); QVariant container = getVar(containerName, context); - if(container.type() != QVariant::List) + if(typeId(container) != QMetaType::QVariantList) { *error = "\"for\" container must be a list"; return QVariantList(); diff -Nru pushpin-1.38.0/src/cpp/runner/zurlservice.cpp pushpin-1.39.1/src/cpp/runner/zurlservice.cpp --- pushpin-1.38.0/src/cpp/runner/zurlservice.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/zurlservice.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -34,9 +34,7 @@ const QString &logDir, const QString &ipcPrefix, const QString &filePrefix, - int logLevel, - QObject *parent) : - Service(parent) + int logLevel) { args_ += binFile; args_ += "--config=" + QDir(runDir).filePath(filePrefix + "zurl.conf"); diff -Nru pushpin-1.38.0/src/cpp/runner/zurlservice.h pushpin-1.39.1/src/cpp/runner/zurlservice.h --- pushpin-1.38.0/src/cpp/runner/zurlservice.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/runner/zurlservice.h 2024-03-18 18:42:24.000000000 +0000 @@ -27,8 +27,6 @@ class ZurlService : public Service { - Q_OBJECT - public: ZurlService( const QString &binFile, @@ -37,8 +35,7 @@ const QString &logDir, const QString &ipcPrefix, const QString &filePrefix, - int logLevel, - QObject *parent = 0); + int logLevel); // reimplemented diff -Nru pushpin-1.38.0/src/cpp/settings.cpp pushpin-1.39.1/src/cpp/settings.cpp --- pushpin-1.38.0/src/cpp/settings.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/settings.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -26,6 +27,7 @@ #include #include #include +#include "qtcompat.h" #include "config.h" Settings::Settings(const QString &fileName) : @@ -153,11 +155,11 @@ QVariant v = valueRaw(key, defaultValue); if(v.isValid()) { - if(v.type() == QVariant::String) + if(typeId(v) == QMetaType::QString) { v = resolveVars(v.toString()); } - else if(v.type() == QVariant::StringList) + else if(typeId(v) == QMetaType::QStringList) { QStringList oldList = v.toStringList(); QStringList newList; diff -Nru pushpin-1.38.0/src/cpp/simplehttpserver.cpp pushpin-1.39.1/src/cpp/simplehttpserver.cpp --- pushpin-1.38.0/src/cpp/simplehttpserver.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/simplehttpserver.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -91,9 +91,15 @@ void start(QTcpSocket *_sock) { - connect(_sock, &QTcpSocket::readyRead, this, &Private::sock_readyRead); - connect(_sock, &QTcpSocket::bytesWritten, this, &Private::sock_bytesWritten); - connect(_sock, &QTcpSocket::disconnected, this, &Private::sock_disconnected); + QObject::connect(_sock, &QTcpSocket::readyRead, [this]() { + this->sock_readyRead(); + }); + QObject::connect(_sock, &QTcpSocket::bytesWritten, this, [this](qint64 bytes) { + this->sock_bytesWritten(bytes); + }); + QObject::connect(_sock, &QTcpSocket::disconnected, [this]() { + this->sock_disconnected(); + }); sock = _sock; sock->setParent(this); @@ -103,9 +109,15 @@ void start(QLocalSocket *_sock) { - connect(_sock, &QLocalSocket::readyRead, this, &Private::sock_readyRead); - connect(_sock, &QLocalSocket::bytesWritten, this, &Private::sock_bytesWritten); - connect(_sock, &QLocalSocket::disconnected, this, &Private::sock_disconnected); + QObject::connect(_sock, &QLocalSocket::readyRead, [this]() { + this->sock_readyRead(); + }); + QObject::connect(_sock, &QLocalSocket::bytesWritten, this, [this](qint64 bytes) { + this->sock_bytesWritten(bytes); + }); + QObject::connect(_sock, &QLocalSocket::disconnected, [this]() { + this->sock_disconnected(); + }); sock = _sock; sock->setParent(this); @@ -342,7 +354,6 @@ } } -private slots: void sock_readyRead() { if(state == ReadHeader || state == ReadBody) @@ -551,8 +562,7 @@ if(!d->pending.isEmpty()) { SimpleHttpRequest *req = d->pending.takeFirst(); - auto conn = d->finishedConnections.find(req); - d->finishedConnections.erase(conn); + d->finishedConnections.erase(req); return req; } diff -Nru pushpin-1.38.0/src/cpp/statsmanager.cpp pushpin-1.39.1/src/cpp/statsmanager.cpp --- pushpin-1.38.0/src/cpp/statsmanager.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/statsmanager.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2014-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -44,6 +44,7 @@ #define ACTIVITY_TIMEOUT 100 #define REFRESH_INTERVAL 1000 #define EXTERNAL_CONNECTIONS_MAX_INTERVAL 10000 +#define EXPIRE_MAX 10000 #define SHOULD_PROCESS_TIME(x) (x * 3 / 4) @@ -96,7 +97,7 @@ bool linger; qint64 lastReport; qint64 retrySeq; - QByteArray from; // external + QByteArray from; // external or linger source int ttl; // external qint64 lastActive; // external @@ -207,6 +208,18 @@ } }; + class RetryInfo + { + public: + quint64 nextSeq; + QMap connectionInfoBySeq; + + RetryInfo() : + nextSeq(0) + { + } + }; + class Report { public: @@ -397,7 +410,7 @@ QHash routeActivity; QHash connectionInfoById; QHash > connectionInfoByRoute; - QMap connectionInfoByRetrySeq; + QHash retryInfoBySource; QVector > connectionInfoRefreshBuckets; int currentConnectionInfoRefreshBucket; QHash > externalConnectionInfoByFrom; @@ -412,7 +425,6 @@ QHash reports; Counts combinedCounts; Report combinedReport; - quint64 nextRetrySeq; QTimer *activityTimer; QTimer *reportTimer; QTimer *refreshTimer; @@ -439,7 +451,6 @@ currentConnectionInfoRefreshBucket(0), currentSubscriptionRefreshBucket(0), wheel(TimerWheel((_connectionsMax * 2) + _subscriptionsMax)), - nextRetrySeq(0), reportTimer(0) { activityTimer = new QTimer(this); @@ -472,28 +483,36 @@ { if(activityTimer) { - activityTimer->setParent(0); activityTimer->disconnect(this); + activityTimer->setParent(0); activityTimer->deleteLater(); activityTimer = 0; } if(reportTimer) { - reportTimer->setParent(0); reportTimer->disconnect(this); + reportTimer->setParent(0); reportTimer->deleteLater(); reportTimer = 0; } if(refreshTimer) { - refreshTimer->setParent(0); refreshTimer->disconnect(this); + refreshTimer->setParent(0); refreshTimer->deleteLater(); refreshTimer = 0; } + if(externalConnectionsMaxTimer) + { + externalConnectionsMaxTimer->disconnect(this); + externalConnectionsMaxTimer->setParent(0); + externalConnectionsMaxTimer->deleteLater(); + externalConnectionsMaxTimer = 0; + } + qDeleteAll(connectionInfoById); QMutableHashIterator > it(externalConnectionInfoByFrom); @@ -537,6 +556,7 @@ { if(!prometheusServer->listenLocal(portStr.mid(6))) { + promServerConnection.disconnect(); delete prometheusServer; return false; @@ -560,6 +580,7 @@ if(!prometheusServer->listen(addr, port)) { + promServerConnection.disconnect(); delete prometheusServer; return false; @@ -621,8 +642,8 @@ } else if(reportInterval <= 0 && reportTimer) { - reportTimer->setParent(0); reportTimer->disconnect(this); + reportTimer->setParent(0); reportTimer->deleteLater(); reportTimer = 0; } @@ -718,7 +739,15 @@ } if(c->retrySeq >= 0) - connectionInfoByRetrySeq.remove(c->retrySeq); + { + RetryInfo &ri = retryInfoBySource[c->from]; + ri.connectionInfoBySeq.remove(c->retrySeq); + + // FIXME: we keep the source entry even when there are no more + // connections, to avoid resetting the seq value. if there is + // a lot of proxy instance churn, retryInfoBySource could + // fill up with unused entries that will never be cleaned up. + } if(c->lastRefresh >= 0) { @@ -764,20 +793,25 @@ wheelRemove(c); } - void removeLingeringConnections(quint64 retrySeq) + void removeLingeringConnections(const QByteArray &source, quint64 retrySeq) { + if(!retryInfoBySource.contains(source)) + return; + + RetryInfo &ri = retryInfoBySource[source]; + // invalid retry seq - if(retrySeq >= nextRetrySeq) + if(retrySeq >= ri.nextSeq) return; QList toRemove; - QMap::iterator it = connectionInfoByRetrySeq.find(retrySeq); - while(it != connectionInfoByRetrySeq.end()) + QMap::iterator it = ri.connectionInfoBySeq.find(retrySeq); + while(it != ri.connectionInfoBySeq.end()) { toRemove += it.value(); - if(it == connectionInfoByRetrySeq.begin()) + if(it == ri.connectionInfoBySeq.begin()) break; --it; @@ -992,7 +1026,7 @@ void sendConnectionsMax(const QByteArray &routeId, ConnectionsMax *cm, qint64 now) { - emit q->connMax(getConnMaxPacket(routeId, cm, now)); + q->connMax(getConnMaxPacket(routeId, cm, now)); } void updateConnectionsMax(const QByteArray &routeId, qint64 now) @@ -1074,7 +1108,7 @@ QList refreshedConnIds; QSet routesUpdated; - while(true) + for(int i = 0; i < EXPIRE_MAX; ++i) { TimerWheel::Expired expired = wheel.takeExpired(); @@ -1151,7 +1185,7 @@ removeSubscription(s); delete s; - emit q->unsubscribed(mode, channel); + q->unsubscribed(mode, channel); } else { @@ -1167,7 +1201,7 @@ } if(!refreshedConnIds.isEmpty()) - emit q->connectionsRefreshed(refreshedConnIds); + q->connectionsRefreshed(refreshedConnIds); foreach(const QByteArray &routeId, routesUpdated) updateConnectionsMax(routeId, now); @@ -1195,7 +1229,7 @@ } if(!refreshedIds.isEmpty()) - emit q->connectionsRefreshed(refreshedIds); + q->connectionsRefreshed(refreshedIds); ++currentConnectionInfoRefreshBucket; if(currentConnectionInfoRefreshBucket >= connectionInfoRefreshBuckets.count()) @@ -1280,7 +1314,7 @@ void mergeExternalConnectionsMax(const StatsPacket &packet, qint64 now) { if(packet.retrySeq >= 0) - removeLingeringConnections((quint64)packet.retrySeq); + removeLingeringConnections(packet.from, (quint64)packet.retrySeq); QHash &maxes = externalConnectionsMaxes[packet.route].maxes; @@ -1396,7 +1430,7 @@ if(sock) write(p); - emit q->reported(QList() << p); + q->reported(QList() << p); } StatsPacket getConnMaxPacket(const QByteArray &routeId, ConnectionsMax *cm, qint64 now) @@ -1499,7 +1533,7 @@ } if(!reportPackets.isEmpty()) - emit q->reported(reportPackets); + q->reported(reportPackets); } void refresh_timeout() @@ -1747,7 +1781,7 @@ d->sendConnected(c); } -int StatsManager::removeConnection(const QByteArray &id, bool linger) +int StatsManager::removeConnection(const QByteArray &id, bool linger, const QByteArray &source) { Private::ConnectionInfo *c = d->connectionInfoById.value(id); if(!c) @@ -1765,9 +1799,16 @@ if(!c->linger) { c->linger = true; - c->retrySeq = (qint64)d->nextRetrySeq++; - d->connectionInfoByRetrySeq.insert((quint64)c->retrySeq, c); + if(!d->retryInfoBySource.contains(source)) + d->retryInfoBySource.insert(source, Private::RetryInfo()); + + Private::RetryInfo &ri = d->retryInfoBySource[source]; + + c->from = source; + c->retrySeq = (qint64)ri.nextSeq++; + + ri.connectionInfoBySeq.insert((quint64)c->retrySeq, c); // hack to ensure full linger time honored by refresh processing qint64 lingerStartTime = now + (d->connectionLinger - SHOULD_PROCESS_TIME(d->connectionTtl)); @@ -1876,7 +1917,7 @@ d->removeSubscription(s); delete s; - emit unsubscribed(mode, channel); + unsubscribed(mode, channel); } } @@ -2087,9 +2128,14 @@ d->flushReport(routeId); } -qint64 StatsManager::lastRetrySeq() const +qint64 StatsManager::lastRetrySeq(const QByteArray &source) const { - return ((qint64)d->nextRetrySeq) - 1; + if(!d->retryInfoBySource.contains(source)) + return -1; + + Private::RetryInfo &ri = d->retryInfoBySource[source]; + + return ((qint64)ri.nextSeq) - 1; } StatsPacket StatsManager::getConnMaxPacket(const QByteArray &routeId) diff -Nru pushpin-1.38.0/src/cpp/statsmanager.h pushpin-1.39.1/src/cpp/statsmanager.h --- pushpin-1.38.0/src/cpp/statsmanager.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/statsmanager.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2014-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -27,6 +27,7 @@ #include #include "packet/statspacket.h" #include "stats.h" +#include class QHostAddress; @@ -72,7 +73,7 @@ void addMessage(const QString &channel, const QString &itemId, const QString &transport, quint32 count = 1, int blocks = -1); void addConnection(const QByteArray &id, const QByteArray &routeId, ConnectionType type, const QHostAddress &peerAddress, bool ssl, bool quiet, int reportOffset = -1); - int removeConnection(const QByteArray &id, bool linger); // return unreported time + int removeConnection(const QByteArray &id, bool linger, const QByteArray &source = QByteArray()); // return unreported time // manager automatically refreshes, but it may be useful to force a // send before removing with linger @@ -103,16 +104,15 @@ void flushReport(const QByteArray &routeId); - qint64 lastRetrySeq() const; + qint64 lastRetrySeq(const QByteArray &source) const; StatsPacket getConnMaxPacket(const QByteArray &routeId); void setRetrySeq(const QByteArray &routeId, int value); -signals: - void connectionsRefreshed(const QList &ids); - void unsubscribed(const QString &mode, const QString &channel); - void reported(const QList &packet); - void connMax(const StatsPacket &packet); + boost::signals2::signal&)> connectionsRefreshed; + boost::signals2::signal unsubscribed; + boost::signals2::signal&)> reported; + boost::signals2::signal connMax; private: class Private; diff -Nru pushpin-1.38.0/src/cpp/tests/handlerenginetest.cpp pushpin-1.39.1/src/cpp/tests/handlerenginetest.cpp --- pushpin-1.38.0/src/cpp/tests/handlerenginetest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/tests/handlerenginetest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -25,6 +26,7 @@ #include "qzmqsocket.h" #include "qzmqvalve.h" #include "qzmqreqmessage.h" +#include "qtcompat.h" #include "log.h" #include "tnetstring.h" #include "zhttprequestpacket.h" @@ -61,6 +63,10 @@ bool serverFailed; int serverOutSeq; QByteArray requestBody; + Connection zhttpClientInValveConnection; + Connection zhttpServerInValveConnection; + Connection zhttpServerInStreamValveConnection; + Connection proxyAcceptValveConnection; Wrapper(QObject *parent, QDir _workDir) : QObject(parent), @@ -77,16 +83,16 @@ zhttpClientInSock = new QZmq::Socket(QZmq::Socket::Sub, this); zhttpClientInValve = new QZmq::Valve(zhttpClientInSock, this); - connect(zhttpClientInValve, &QZmq::Valve::readyRead, this, &Wrapper::zhttpClientIn_readyRead); + zhttpClientInValveConnection = zhttpClientInValve->readyRead.connect(boost::bind(&Wrapper::zhttpClientIn_readyRead, this, boost::placeholders::_1)); zhttpServerInSock = new QZmq::Socket(QZmq::Socket::Pull, this); zhttpServerInValve = new QZmq::Valve(zhttpServerInSock, this); - connect(zhttpServerInValve, &QZmq::Valve::readyRead, this, &Wrapper::zhttpServerIn_readyRead); + zhttpServerInValveConnection = zhttpServerInValve->readyRead.connect(boost::bind(&Wrapper::zhttpServerIn_readyRead, this, boost::placeholders::_1)); zhttpServerInStreamSock = new QZmq::Socket(QZmq::Socket::Router, this); zhttpServerInStreamSock->setIdentity("test-server"); zhttpServerInStreamValve = new QZmq::Valve(zhttpServerInStreamSock, this); - connect(zhttpServerInStreamValve, &QZmq::Valve::readyRead, this, &Wrapper::zhttpServerInStream_readyRead); + zhttpServerInStreamValveConnection = zhttpServerInStreamValve->readyRead.connect(boost::bind(&Wrapper::zhttpServerInStream_readyRead, this, boost::placeholders::_1)); zhttpServerOutSock = new QZmq::Socket(QZmq::Socket::Pub, this); @@ -94,7 +100,7 @@ proxyAcceptSock = new QZmq::Socket(QZmq::Socket::Dealer, this); proxyAcceptValve = new QZmq::Valve(proxyAcceptSock, this); - connect(proxyAcceptValve, &QZmq::Valve::readyRead, this, &Wrapper::proxyAccept_readyRead); + proxyAcceptValveConnection = proxyAcceptValve->readyRead.connect(boost::bind(&Wrapper::proxyAccept_readyRead, this, boost::placeholders::_1)); // publish sockets @@ -241,7 +247,7 @@ QZmq::ReqMessage message(_message); QVariant v = TnetString::toVariant(message.content()[0]); - QVERIFY(v.type() == QVariant::Hash); + QVERIFY(typeId(v) == QMetaType::QVariantHash); QVariantHash vresp = v.toHash(); QVERIFY(vresp.value("success").toBool()); @@ -249,7 +255,7 @@ acceptSuccess = true; v = vresp.value("value"); - QVERIFY(v.type() == QVariant::Hash); + QVERIFY(typeId(v) == QMetaType::QVariantHash); acceptValue = v.toHash(); } }; @@ -286,7 +292,7 @@ config.clientOutSpecs = QStringList() << ("ipc://" + workDir.filePath("server-in")); config.clientOutStreamSpecs = QStringList() << ("ipc://" + workDir.filePath("server-in-stream")); config.clientInSpecs = QStringList() << ("ipc://" + workDir.filePath("server-out")); - config.acceptSpec = ("ipc://" + workDir.filePath("accept")); + config.acceptSpecs = QStringList() << ("ipc://" + workDir.filePath("accept")); config.pushInSpec = ("ipc://" + workDir.filePath("publish-pull")); config.connectionSubscriptionMax = 20; config.connectionsMax = 20; diff -Nru pushpin-1.38.0/src/cpp/tests/jsonpatchtest.cpp pushpin-1.39.1/src/cpp/tests/jsonpatchtest.cpp --- pushpin-1.38.0/src/cpp/tests/jsonpatchtest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/tests/jsonpatchtest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -21,6 +22,7 @@ */ #include +#include "qtcompat.h" #include "jsonpatch.h" class JsonPatchTest : public QObject @@ -49,7 +51,7 @@ ret = JsonPatch::patch(data, QVariantList() << op); QVERIFY(ret.isValid()); data = ret.toMap(); - QCOMPARE(data["fruit"].type(), QVariant::List); + QCOMPARE(typeId(data["fruit"]), QMetaType::QVariantList); QCOMPARE(data["fruit"].toList()[0].toString(), QString("apple")); op.clear(); @@ -71,7 +73,7 @@ ret = JsonPatch::patch(data, QVariantList() << op); QVERIFY(ret.isValid()); data = ret.toMap(); - QCOMPARE(data["fruit"].toList()[1].type(), QVariant::Map); + QCOMPARE(typeId(data["fruit"].toList()[1]), QMetaType::QVariantMap); QCOMPARE(data["fruit"].toList()[1].toMap().value("cherries").toBool(), true); QCOMPARE(data["fruit"].toList()[1].toMap().value("grapes").toInt(), 5); diff -Nru pushpin-1.38.0/src/cpp/tests/jwttest.cpp pushpin-1.39.1/src/cpp/tests/jwttest.cpp --- pushpin-1.38.0/src/cpp/tests/jwttest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/tests/jwttest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2013-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -23,6 +24,7 @@ #include #include #include +#include "qtcompat.h" #include "jwt.h" static const char *test_ec_private_key_pem = @@ -87,7 +89,7 @@ void validToken() { QVariant vclaim = Jwt::decode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJmb28iOiAiYmFyIn0.oBia0Fph39FwQWv0TS7Disg4qa0aFa8qpMaYDrIXZqs", Jwt::DecodingKey::fromSecret("secret")); - QVERIFY(vclaim.type() == QVariant::Map); + QVERIFY(typeId(vclaim) == QMetaType::QVariantMap); QVariantMap claim = vclaim.toMap(); QVERIFY(claim.value("foo") == "bar"); } @@ -100,7 +102,7 @@ key += 0x80; key += 0xfe; QVariant vclaim = Jwt::decode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJmb28iOiAiYmFyIn0.-eLxyGEITnd6IP4WvGJx9CmIOt--Qcs3LB6wblJ7KXI", Jwt::DecodingKey::fromSecret(key)); - QVERIFY(vclaim.type() == QVariant::Map); + QVERIFY(typeId(vclaim) == QMetaType::QVariantMap); QVariantMap claim = vclaim.toMap(); QVERIFY(claim.value("foo") == "bar"); } diff -Nru pushpin-1.38.0/src/cpp/tests/proxyenginetest.cpp pushpin-1.39.1/src/cpp/tests/proxyenginetest.cpp --- pushpin-1.38.0/src/cpp/tests/proxyenginetest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/tests/proxyenginetest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2013-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -21,8 +22,8 @@ */ #include +#include #include -#include #include #include #include @@ -39,6 +40,7 @@ #include "rtimer.h" #include "zhttpmanager.h" #include "statsmanager.h" +#include "domainmap.h" #include "engine.h" Q_DECLARE_METATYPE(QList); @@ -85,6 +87,11 @@ int clientReqsFinished; QByteArray requestBody; QHash responses; + Connection zhttpClientInValveConnection; + Connection zhttpServerInValveConnection; + Connection zhttpServerInStreamValveConnection; + Connection handlerAcceptValveConnection; + Connection handlerInspectValveConnection; Wrapper(QObject *parent, QDir _workDir) : QObject(parent), @@ -106,16 +113,16 @@ zhttpClientInSock = new QZmq::Socket(QZmq::Socket::Sub, this); zhttpClientInValve = new QZmq::Valve(zhttpClientInSock, this); - connect(zhttpClientInValve, &QZmq::Valve::readyRead, this, &Wrapper::zhttpClientIn_readyRead); + zhttpClientInValveConnection = zhttpClientInValve->readyRead.connect(boost::bind(&Wrapper::zhttpClientIn_readyRead, this, boost::placeholders::_1)); zhttpServerInSock = new QZmq::Socket(QZmq::Socket::Pull, this); zhttpServerInValve = new QZmq::Valve(zhttpServerInSock, this); - connect(zhttpServerInValve, &QZmq::Valve::readyRead, this, &Wrapper::zhttpServerIn_readyRead); + zhttpServerInValveConnection = zhttpServerInValve->readyRead.connect(boost::bind(&Wrapper::zhttpServerIn_readyRead, this, boost::placeholders::_1)); zhttpServerInStreamSock = new QZmq::Socket(QZmq::Socket::Router, this); zhttpServerInStreamSock->setIdentity("test-server"); zhttpServerInStreamValve = new QZmq::Valve(zhttpServerInStreamSock, this); - connect(zhttpServerInStreamValve, &QZmq::Valve::readyRead, this, &Wrapper::zhttpServerInStream_readyRead); + zhttpServerInStreamValveConnection = zhttpServerInStreamValve->readyRead.connect(boost::bind(&Wrapper::zhttpServerInStream_readyRead, this, boost::placeholders::_1)); zhttpServerOutSock = new QZmq::Socket(QZmq::Socket::Pub, this); @@ -125,12 +132,12 @@ handlerAcceptSock = new QZmq::Socket(QZmq::Socket::Router, this); handlerAcceptValve = new QZmq::Valve(handlerAcceptSock, this); - connect(handlerAcceptValve, &QZmq::Valve::readyRead, this, &Wrapper::handlerAccept_readyRead); + handlerAcceptValveConnection = handlerAcceptValve->readyRead.connect(boost::bind(&Wrapper::handlerAccept_readyRead, this, boost::placeholders::_1)); handlerInspectValve = new QZmq::Valve(handlerInspectSock, this); - connect(handlerInspectValve, &QZmq::Valve::readyRead, this, &Wrapper::handlerInspect_readyRead); + handlerInspectValveConnection = handlerInspectValve->readyRead.connect(boost::bind(&Wrapper::handlerInspect_readyRead, this, boost::placeholders::_1)); - handlerRetryOutSock = new QZmq::Socket(QZmq::Socket::Push, this); + handlerRetryOutSock = new QZmq::Socket(QZmq::Socket::Router, this); } void startHttp() @@ -178,7 +185,7 @@ responses.clear(); } -private slots: +private: void zhttpClientIn_readyRead(const QList &message) { log_debug("client in"); @@ -535,7 +542,12 @@ vretry["request-data"] = vaccept["request-data"]; QByteArray buf = TnetString::fromVariant(vretry); log_debug("retrying: %s", qPrintable(TnetString::variantToString(vretry, -1))); - handlerRetryOutSock->write(QList() << buf); + + QList msg; + msg.append("proxy"); + msg.append(QByteArray()); + msg.append(buf); + handlerRetryOutSock->write(msg); return; } } @@ -552,14 +564,22 @@ Q_OBJECT private: + DomainMap *domainMap; Engine *engine; Wrapper *wrapper; + QList trackedPackets; private: void reset() { wrapper->reset(); engine->statsManager()->flushReport(QByteArray()); + trackedPackets.clear(); + } + + void appendTrackedPackets(const QList& packets) + { + trackedPackets.append(packets); } private slots: @@ -578,7 +598,9 @@ wrapper = new Wrapper(this, workDir); wrapper->startHttp(); - engine = new Engine(this); + domainMap = new DomainMap(configDir.filePath("routes"), this); + + engine = new Engine(domainMap, this); Engine::Configuration config; config.clientId = "proxy"; @@ -592,12 +614,11 @@ config.acceptSpec = ("ipc://" + workDir.filePath("accept")); config.retryInSpec = ("ipc://" + workDir.filePath("retry-out")); config.statsSpec = ("ipc://" + workDir.filePath("stats")); + config.sessionsMax = 20; config.inspectTimeout = 500; config.inspectPrefetch = 5; - config.routesFile = configDir.filePath("routes"); config.sigIss = "pushpin"; config.sigKey = Jwt::EncodingKey::fromSecret("changeme"); - config.connectionsMax = 20; config.statsConnectionTtl = 120; config.statsReportInterval = 1000; // set a large interval so there's only one working report QVERIFY(engine->start(config)); @@ -610,8 +631,10 @@ void cleanupTestCase() { delete engine; + delete domainMap; delete wrapper; + QCoreApplication::instance()->sendPostedEvents(); RTimer::deinit(); } @@ -619,7 +642,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -643,12 +668,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); - - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 23); // "GET" + "/path?a=b" + "Host" + "example" QCOMPARE(p.clientContentBytesReceived, 0); QCOMPARE(p.clientHeaderBytesSent, 43); // "200" + "OK" + "Content-Type" + "text/plain" + "Content-Length" + "11" @@ -740,7 +762,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -785,12 +809,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); - - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 9); // "POST" + "/path" QCOMPARE(p.clientContentBytesReceived, 11); // "hello world" QCOMPARE(p.clientHeaderBytesSent, 43); // "200" + "OK" + "Content-Type" + "text/plain" + "Content-Length" + "11" @@ -805,7 +826,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -847,12 +870,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); - - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 9); // "POST" + "/path" QCOMPARE(p.clientContentBytesReceived, 5); // "hello" QCOMPARE(p.clientHeaderBytesSent, 0); @@ -867,7 +887,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -891,12 +913,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); - - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 22); // "GET" + "/path?hold=response" QCOMPARE(p.clientContentBytesReceived, 0); QCOMPARE(p.clientHeaderBytesSent, 0); @@ -911,7 +930,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -936,12 +957,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); - - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 20); // "GET" + "/path?hold=stream" QCOMPARE(p.clientContentBytesReceived, 0); QCOMPARE(p.clientHeaderBytesSent, 0); @@ -977,7 +995,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -999,12 +1019,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); - - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 18); // "GET" + "/path?hold=none" QCOMPARE(p.clientContentBytesReceived, 0); QCOMPARE(p.clientHeaderBytesSent, 43); // "200" + "OK" + "Content-Type" + "text/plain" + "Content-Length" + "11" @@ -1040,7 +1057,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -1066,12 +1085,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); - - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 31); // "GET" + "/path?hold=stream&large=true" QCOMPARE(p.clientContentBytesReceived, 0); QCOMPARE(p.clientHeaderBytesSent, 5); // "200" + "OK" @@ -1086,7 +1102,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -1111,12 +1129,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); - - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 29); // "GET" + "/path?hold=none&large=true" QCOMPARE(p.clientContentBytesReceived, 0); QCOMPARE(p.clientHeaderBytesSent, 27); // "200" + "OK" + "Content-Type" + "text/plain" @@ -1131,7 +1146,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -1166,12 +1183,9 @@ headerBytes += ZhttpManager::estimateRequestHeaderBytes(req2Data.method, req2Data.uri, req2Data.headers); contentBytes += req2Data.body.size(); - QCOMPARE(spy.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); - - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 42); // "GET" + "/path2?hold=response&body-instruct=true" QCOMPARE(p.clientContentBytesReceived, 0); QCOMPARE(p.clientHeaderBytesSent, 43); // "200" + "OK" + "Content-Type" + "text/plain" + "Content-Length" + "11" @@ -1186,7 +1200,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); wrapper->sharingKey = "test"; @@ -1228,12 +1244,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); - - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 16); // "GET" + "/path" + "GET" + "/path" QCOMPARE(p.clientContentBytesReceived, 0); QCOMPARE(p.clientHeaderBytesSent, 86); // "200" + "OK" + "Content-Type" + "text/plain" + "Content-Length" + "11" + "200" + "OK" + "Content-Type" + "text/plain" + "Content-Length" + "11" @@ -1248,7 +1261,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); wrapper->sharingKey = "test"; @@ -1321,12 +1336,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); - - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 18); // "POST" + "/path" + "POST" + "/path" QCOMPARE(p.clientContentBytesReceived, 22); // "hello world" + "hello world" QCOMPARE(p.clientHeaderBytesSent, 86); // "200" + "OK" + "Content-Type" + "text/plain" + "Content-Length" + "11" + "200" + "OK" + "Content-Type" + "text/plain" + "Content-Length" + "11" @@ -1341,7 +1353,9 @@ { reset(); - QSignalSpy spy(engine->statsManager(), SIGNAL(reported(const QList &))); + boost::signals2::scoped_connection reportConnection = engine->statsManager()->reported.connect( + boost::bind(&ProxyEngineTest::appendTrackedPackets, this, boost::placeholders::_1) + ); ZhttpRequestPacket zreq; zreq.from = "test-client"; @@ -1379,12 +1393,9 @@ HttpRequestData reqData = QHashIterator(wrapper->serverReqs).next().value(); - QCOMPARE(spy.count(), 1); - - QList packets = qvariant_cast>(spy.takeFirst().at(0)); - QCOMPARE(packets.count(), 1); + QCOMPARE(trackedPackets.size(), 1); - StatsPacket p = packets[0]; + StatsPacket p = trackedPackets.takeFirst(); QCOMPARE(p.clientHeaderBytesReceived, 8); // "GET" + "/path" QCOMPARE(p.clientContentBytesReceived, 5); QCOMPARE(p.clientHeaderBytesSent, 22); // "101" + "Switching Protocols" diff -Nru pushpin-1.38.0/src/cpp/tests/tests.pro pushpin-1.39.1/src/cpp/tests/tests.pro --- pushpin-1.38.0/src/cpp/tests/tests.pro 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/tests/tests.pro 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ TEMPLATE = lib CONFIG -= app_bundle -CONFIG += staticlib c++11 +CONFIG += staticlib c++14 QT -= gui QT *= network testlib TARGET = pushpin-cpptest diff -Nru pushpin-1.38.0/src/cpp/tnetstring.cpp pushpin-1.39.1/src/cpp/tnetstring.cpp --- pushpin-1.38.0/src/cpp/tnetstring.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/tnetstring.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2022 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * $FANOUT_BEGIN_LICENSE:APACHE2$ * @@ -21,6 +22,7 @@ #include "tnetstring.h" #include +#include "qtcompat.h" namespace TnetString { @@ -54,22 +56,22 @@ QByteArray fromVariant(const QVariant &in) { - switch(in.type()) + switch(typeId(in)) { - case QVariant::ByteArray: + case QMetaType::QByteArray: return fromByteArray(in.toByteArray()); - case QVariant::Double: + case QMetaType::Double: return fromDouble(in.toDouble()); - case QVariant::Bool: + case QMetaType::Bool: return fromBool(in.toBool()); - case QVariant::Invalid: + case QMetaType::UnknownType: return fromNull(); - case QVariant::Hash: + case QMetaType::QVariantHash: return fromHash(in.toHash()); - case QVariant::List: + case QMetaType::QVariantList: return fromList(in.toList()); default: - if(in.canConvert(QVariant::LongLong)) + if(canConvert(in, QMetaType::LongLong)) return fromInt(in.toLongLong()); // unsupported type @@ -373,8 +375,8 @@ { QString out; - QVariant::Type type = in.type(); - if(type == QVariant::Hash) + QMetaType::Type type = typeId(in); + if(type == QMetaType::QVariantHash) { QVariantHash hash = in.toHash(); @@ -406,7 +408,7 @@ out += QString(indent, ' '); out += '}'; } - else if(type == QVariant::List) + else if(type == QMetaType::QVariantList) { QVariantList list = in.toList(); @@ -435,18 +437,18 @@ out += QString(indent, ' '); out += ']'; } - else if(type == QVariant::ByteArray) + else if(type == QMetaType::QByteArray) { QByteArray val = in.toByteArray(); out += '\"' + byteArrayToEscapedString(val) + '\"'; } - else if(type == QVariant::Double) + else if(type == QMetaType::Double) out += QString::number(in.toDouble()); - else if(type == QVariant::Bool) + else if(type == QMetaType::Bool) out += in.toBool() ? "true" : "false"; - else if(type == QVariant::Invalid) + else if(type == QMetaType::UnknownType) out += "null"; - else if(in.canConvert(QVariant::LongLong)) + else if(canConvert(in, QMetaType::LongLong)) out += QString::number(in.toLongLong()); else out += QString("").arg((int)type); diff -Nru pushpin-1.38.0/src/cpp/websocket.h pushpin-1.39.1/src/cpp/websocket.h --- pushpin-1.38.0/src/cpp/websocket.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/websocket.h 2024-03-18 18:42:24.000000000 +0000 @@ -28,6 +28,9 @@ #include #include #include "httpheaders.h" +#include + +using Signal = boost::signals2::signal; class WebSocket : public QObject { @@ -110,14 +113,13 @@ virtual Frame readFrame() = 0; virtual void close(int code = -1, const QString &reason = QString()) = 0; -signals: - void connected(); - void readyRead(); - void framesWritten(int count, int contentBytes); - void writeBytesChanged(); - void peerClosed(); // emitted only if peer closes before we do - void closed(); // emitted after peer acks our close, or immediately if we were acking - void error(); + Signal connected; + Signal readyRead; + boost::signals2::signal framesWritten; + Signal writeBytesChanged; + Signal peerClosed; // emitted only if peer closes before we do + Signal closed; // emitted after peer acks our close, or immediately if we were acking + Signal error; }; #endif diff -Nru pushpin-1.38.0/src/cpp/zhttpmanager.cpp pushpin-1.39.1/src/cpp/zhttpmanager.cpp --- pushpin-1.38.0/src/cpp/zhttpmanager.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zhttpmanager.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -105,6 +105,13 @@ QHash keepAliveRegistrations; QSet sessionRefreshBuckets[ZHTTP_REFRESH_BUCKETS]; int currentSessionRefreshBucket; + Connection cosConnection; + Connection cossConnection; + Connection sosConnection; + Connection rrConnection; + Connection clientConnection; + Connection serverConnection; + Connection serverStreamConnection; Private(ZhttpManager *_q) : QObject(_q), @@ -156,11 +163,13 @@ bool setupClientOut() { + cosConnection.disconnect(); + rrConnection.disconnect(); delete client_req_sock; delete client_out_sock; client_out_sock = new QZmq::Socket(QZmq::Socket::Push, this); - connect(client_out_sock, &QZmq::Socket::messagesWritten, this, &Private::client_out_messagesWritten); + cosConnection = client_out_sock->messagesWritten.connect(boost::bind(&Private::client_out_messagesWritten, this, boost::placeholders::_1)); client_out_sock->setHwm(OUT_HWM); client_out_sock->setShutdownWaitTime(CLIENT_WAIT_TIME); @@ -177,11 +186,13 @@ bool setupClientOutStream() { + rrConnection.disconnect(); + cossConnection.disconnect(); delete client_req_sock; delete client_out_stream_sock; client_out_stream_sock = new QZmq::Socket(QZmq::Socket::Router, this); - connect(client_out_stream_sock, &QZmq::Socket::messagesWritten, this, &Private::client_out_stream_messagesWritten); + cossConnection = client_out_stream_sock->messagesWritten.connect(boost::bind(&Private::client_out_stream_messagesWritten, this, boost::placeholders::_1)); client_out_stream_sock->setWriteQueueEnabled(false); client_out_stream_sock->setHwm(DEFAULT_HWM); @@ -200,6 +211,7 @@ bool setupClientIn() { + rrConnection.disconnect(); delete client_req_sock; delete client_in_sock; @@ -217,7 +229,7 @@ } client_in_valve = new QZmq::Valve(client_in_sock, this); - connect(client_in_valve, &QZmq::Valve::readyRead, this, &Private::client_in_readyRead); + clientConnection = client_in_valve->readyRead.connect(boost::bind(&Private::client_in_readyRead, this, boost::placeholders::_1)); client_in_valve->open(); @@ -226,12 +238,14 @@ bool setupClientReq() { + cosConnection.disconnect(); + cossConnection.disconnect(); delete client_out_sock; delete client_out_stream_sock; delete client_in_sock; client_req_sock = new QZmq::Socket(QZmq::Socket::Dealer, this); - connect(client_req_sock, &QZmq::Socket::readyRead, this, &Private::client_req_readyRead); + rrConnection = client_req_sock->readyRead.connect(boost::bind(&Private::client_req_readyRead, this)); client_req_sock->setHwm(OUT_HWM); client_req_sock->setShutdownWaitTime(CLIENT_WAIT_TIME); @@ -262,7 +276,7 @@ } server_in_valve = new QZmq::Valve(server_in_sock, this); - connect(server_in_valve, &QZmq::Valve::readyRead, this, &Private::server_in_readyRead); + serverConnection = server_in_valve->readyRead.connect(boost::bind(&Private::server_in_readyRead, this, boost::placeholders::_1)); server_in_valve->open(); @@ -271,6 +285,7 @@ bool setupServerInStream() { + serverStreamConnection.disconnect(); delete server_in_stream_sock; server_in_stream_sock = new QZmq::Socket(QZmq::Socket::Router, this); @@ -286,7 +301,7 @@ } server_in_stream_valve = new QZmq::Valve(server_in_stream_sock, this); - connect(server_in_stream_valve, &QZmq::Valve::readyRead, this, &Private::server_in_stream_readyRead); + serverStreamConnection = server_in_stream_valve->readyRead.connect(boost::bind(&Private::server_in_stream_readyRead, this, boost::placeholders::_1)); server_in_stream_valve->open(); @@ -295,10 +310,11 @@ bool setupServerOut() { + sosConnection.disconnect(); delete server_out_sock; server_out_sock = new QZmq::Socket(QZmq::Socket::Pub, this); - connect(server_out_sock, &QZmq::Socket::messagesWritten, this, &Private::server_out_messagesWritten); + sosConnection = server_out_sock->messagesWritten.connect(boost::bind(&Private::server_out_messagesWritten, this, boost::placeholders::_1)); server_out_sock->setWriteQueueEnabled(false); server_out_sock->setHwm(DEFAULT_HWM); @@ -474,7 +490,6 @@ write(type, zresp, zhttpAddress); } -public slots: void client_out_messagesWritten(int count) { Q_UNUSED(count); @@ -485,6 +500,72 @@ Q_UNUSED(count); } + void server_out_messagesWritten(int count) + { + Q_UNUSED(count); + } + + void client_req_readyRead() + { + QPointer self = this; + + while(client_req_sock->canRead()) + { + QList msg = client_req_sock->read(); + if(msg.count() != 2) + { + log_warning("zhttp/zws client req: received message with parts != 2, skipping"); + continue; + } + + QByteArray dataRaw = msg[1]; + if(dataRaw.length() < 1 || dataRaw[0] != 'T') + { + log_warning("zhttp/zws client req: received message with invalid format (missing type), skipping"); + continue; + } + + QVariant data = TnetString::toVariant(dataRaw.mid(1)); + if(data.isNull()) + { + log_warning("zhttp/zws client req: received message with invalid format (tnetstring parse failed), skipping"); + continue; + } + + if(log_outputLevel() >= LOG_LEVEL_DEBUG) + LogUtil::logVariantWithContent(LOG_LEVEL_DEBUG, data, "body", "zhttp/zws client req: IN"); + + ZhttpResponsePacket p; + if(!p.fromVariant(data)) + { + log_warning("zhttp/zws client req: received message with invalid format (parse failed), skipping"); + continue; + } + + if(p.ids.count() != 1) + { + log_warning("zhttp/zws client req: received message with multiple ids, skipping"); + return; + } + + const ZhttpResponsePacket::Id &id = p.ids.first(); + + ZhttpRequest *req = clientReqsByRid.value(ZhttpRequest::Rid(instanceId, id.id)); + if(req) + { + req->handle(id.id, id.seq, p); + if(!self) + return; + + continue; + } + + log_debug("zhttp/zws client req: received message for unknown request id"); + + // NOTE: we don't respond with a cancel message in req mode + } + } + void client_in_readyRead(const QList &msg) { if(msg.count() != 1) @@ -662,67 +743,6 @@ } } - void client_req_readyRead() - { - QPointer self = this; - - while(client_req_sock->canRead()) - { - QList msg = client_req_sock->read(); - if(msg.count() != 2) - { - log_warning("zhttp/zws client req: received message with parts != 2, skipping"); - continue; - } - - QByteArray dataRaw = msg[1]; - if(dataRaw.length() < 1 || dataRaw[0] != 'T') - { - log_warning("zhttp/zws client req: received message with invalid format (missing type), skipping"); - continue; - } - - QVariant data = TnetString::toVariant(dataRaw.mid(1)); - if(data.isNull()) - { - log_warning("zhttp/zws client req: received message with invalid format (tnetstring parse failed), skipping"); - continue; - } - - if(log_outputLevel() >= LOG_LEVEL_DEBUG) - LogUtil::logVariantWithContent(LOG_LEVEL_DEBUG, data, "body", "zhttp/zws client req: IN"); - - ZhttpResponsePacket p; - if(!p.fromVariant(data)) - { - log_warning("zhttp/zws client req: received message with invalid format (parse failed), skipping"); - continue; - } - - if(p.ids.count() != 1) - { - log_warning("zhttp/zws client req: received message with multiple ids, skipping"); - return; - } - - const ZhttpResponsePacket::Id &id = p.ids.first(); - - ZhttpRequest *req = clientReqsByRid.value(ZhttpRequest::Rid(instanceId, id.id)); - if(req) - { - req->handle(id.id, id.seq, p); - if(!self) - return; - - continue; - } - - log_debug("zhttp/zws client req: received message for unknown request id"); - - // NOTE: we don't respond with a cancel message in req mode - } - } - void server_in_stream_readyRead(const QList &msg) { if(msg.count() != 3) @@ -784,11 +804,7 @@ } } - void server_out_messagesWritten(int count) - { - Q_UNUSED(count); - } - +public slots: void refresh_timeout() { QHash > clientSessionsBySender[2]; // index corresponds to type diff -Nru pushpin-1.38.0/src/cpp/zhttprequest.cpp pushpin-1.39.1/src/cpp/zhttprequest.cpp --- pushpin-1.38.0/src/cpp/zhttprequest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zhttprequest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -137,11 +137,11 @@ multi(false), quiet(false) { - expireTimer = new RTimer(this); + expireTimer = new RTimer; expTimerConnection = expireTimer->timeout.connect(boost::bind(&Private::expire_timeout, this)); expireTimer->setSingleShot(true); - keepAliveTimer = new RTimer(this); + keepAliveTimer = new RTimer; keepAliveTimerConnection = keepAliveTimer->timeout.connect(boost::bind(&Private::keepAlive_timeout, this)); } @@ -161,7 +161,7 @@ if(expireTimer) { - expireTimer->disconnect(this); + expTimerConnection.disconnect(); expireTimer->setParent(0); expireTimer->deleteLater(); expireTimer = 0; @@ -169,7 +169,7 @@ if(keepAliveTimer) { - keepAliveTimer->disconnect(this); + keepAliveTimerConnection.disconnect(); keepAliveTimer->setParent(0); keepAliveTimer->deleteLater(); keepAliveTimer = 0; diff -Nru pushpin-1.38.0/src/cpp/zhttprequestpacket.cpp pushpin-1.39.1/src/cpp/zhttprequestpacket.cpp --- pushpin-1.38.0/src/cpp/zhttprequestpacket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zhttprequestpacket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2016 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * $FANOUT_BEGIN_LICENSE:APACHE2$ * @@ -21,6 +22,7 @@ #include "zhttprequestpacket.h" #include +#include "qtcompat.h" #include "tnetstring.h" QVariant ZhttpRequestPacket::toVariant() const @@ -169,7 +171,7 @@ bool ZhttpRequestPacket::fromVariant(const QVariant &in) { - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) return false; QVariantHash obj = in.toHash(); @@ -177,7 +179,7 @@ from.clear(); if(obj.contains("from")) { - if(obj["from"].type() != QVariant::ByteArray) + if(typeId(obj["from"]) != QMetaType::QByteArray) return false; from = obj["from"].toByteArray(); @@ -186,18 +188,18 @@ ids.clear(); if(obj.contains("id")) { - if(obj["id"].type() == QVariant::ByteArray) + if(typeId(obj["id"]) == QMetaType::QByteArray) { Id id; id.id = obj["id"].toByteArray(); ids += id; } - else if(obj["id"].type() == QVariant::List) + else if(typeId(obj["id"]) == QMetaType::QVariantList) { QVariantList vl = obj["id"].toList(); foreach(const QVariant &v, vl) { - if(v.type() != QVariant::Hash) + if(typeId(v) != QMetaType::QVariantHash) return false; Id id; @@ -206,7 +208,7 @@ if(vh.contains("id")) { - if(vh["id"].type() != QVariant::ByteArray) + if(typeId(vh["id"]) != QMetaType::QByteArray) return false; id.id = vh["id"].toByteArray(); @@ -214,7 +216,7 @@ if(vh.contains("seq")) { - if(!vh["seq"].canConvert(QVariant::Int)) + if(!canConvert(vh["seq"], QMetaType::Int)) return false; id.seq = vh["seq"].toInt(); @@ -229,7 +231,7 @@ if(obj.contains("seq")) { - if(!obj["seq"].canConvert(QVariant::Int)) + if(!canConvert(obj["seq"], QMetaType::Int)) return false; if(ids.isEmpty()) @@ -241,7 +243,7 @@ type = Data; if(obj.contains("type")) { - if(obj["type"].type() != QVariant::ByteArray) + if(typeId(obj["type"]) != QMetaType::QByteArray) return false; QByteArray typeStr = obj["type"].toByteArray(); @@ -273,7 +275,7 @@ condition.clear(); if(obj.contains("condition")) { - if(obj["condition"].type() != QVariant::ByteArray) + if(typeId(obj["condition"]) != QMetaType::QByteArray) return false; condition = obj["condition"].toByteArray(); @@ -283,7 +285,7 @@ credits = -1; if(obj.contains("credits")) { - if(!obj["credits"].canConvert(QVariant::Int)) + if(!canConvert(obj["credits"], QMetaType::Int)) return false; credits = obj["credits"].toInt(); @@ -292,7 +294,7 @@ more = false; if(obj.contains("more")) { - if(obj["more"].type() != QVariant::Bool) + if(typeId(obj["more"]) != QMetaType::Bool) return false; more = obj["more"].toBool(); @@ -301,7 +303,7 @@ stream = false; if(obj.contains("stream")) { - if(obj["stream"].type() != QVariant::Bool) + if(typeId(obj["stream"]) != QMetaType::Bool) return false; stream = obj["stream"].toBool(); @@ -310,7 +312,7 @@ maxSize = -1; if(obj.contains("max-size")) { - if(!obj["max-size"].canConvert(QVariant::Int)) + if(!canConvert(obj["max-size"], QMetaType::Int)) return false; maxSize = obj["max-size"].toInt(); @@ -319,7 +321,7 @@ timeout = -1; if(obj.contains("timeout")) { - if(!obj["timeout"].canConvert(QVariant::Int)) + if(!canConvert(obj["timeout"], QMetaType::Int)) return false; timeout = obj["timeout"].toInt(); @@ -328,7 +330,7 @@ method.clear(); if(obj.contains("method")) { - if(obj["method"].type() != QVariant::ByteArray) + if(typeId(obj["method"]) != QMetaType::QByteArray) return false; method = QString::fromLatin1(obj["method"].toByteArray()); @@ -337,7 +339,7 @@ uri.clear(); if(obj.contains("uri")) { - if(obj["uri"].type() != QVariant::ByteArray) + if(typeId(obj["uri"]) != QMetaType::QByteArray) return false; uri = QUrl::fromEncoded(obj["uri"].toByteArray(), QUrl::StrictMode); @@ -346,7 +348,7 @@ headers.clear(); if(obj.contains("headers")) { - if(obj["headers"].type() != QVariant::List) + if(typeId(obj["headers"]) != QMetaType::QVariantList) return false; foreach(const QVariant &i, obj["headers"].toList()) @@ -355,7 +357,7 @@ if(list.count() != 2) return false; - if(list[0].type() != QVariant::ByteArray || list[1].type() != QVariant::ByteArray) + if(typeId(list[0]) != QMetaType::QByteArray || typeId(list[1]) != QMetaType::QByteArray) return false; headers += HttpHeader(list[0].toByteArray(), list[1].toByteArray()); @@ -365,7 +367,7 @@ body.clear(); if(obj.contains("body")) { - if(obj["body"].type() != QVariant::ByteArray) + if(typeId(obj["body"]) != QMetaType::QByteArray) return false; body = obj["body"].toByteArray(); @@ -374,7 +376,7 @@ contentType.clear(); if(obj.contains("content-type")) { - if(obj["content-type"].type() != QVariant::ByteArray) + if(typeId(obj["content-type"]) != QMetaType::QByteArray) return false; contentType = obj["content-type"].toByteArray(); @@ -383,7 +385,7 @@ code = -1; if(obj.contains("code")) { - if(!obj["code"].canConvert(QVariant::Int)) + if(!canConvert(obj["code"], QMetaType::Int)) return false; code = obj["code"].toInt(); @@ -394,7 +396,7 @@ peerAddress = QHostAddress(); if(obj.contains("peer-address")) { - if(obj["peer-address"].type() != QVariant::ByteArray) + if(typeId(obj["peer-address"]) != QMetaType::QByteArray) return false; peerAddress = QHostAddress(QString::fromUtf8(obj["peer-address"].toByteArray())); @@ -403,7 +405,7 @@ peerPort = -1; if(obj.contains("peer-port")) { - if(!obj["peer-port"].canConvert(QVariant::Int)) + if(!canConvert(obj["peer-port"], QMetaType::Int)) return false; peerPort = obj["peer-port"].toInt(); @@ -412,7 +414,7 @@ connectHost.clear(); if(obj.contains("connect-host")) { - if(obj["connect-host"].type() != QVariant::ByteArray) + if(typeId(obj["connect-host"]) != QMetaType::QByteArray) return false; connectHost = QString::fromUtf8(obj["connect-host"].toByteArray()); @@ -421,7 +423,7 @@ connectPort = -1; if(obj.contains("connect-port")) { - if(!obj["connect-port"].canConvert(QVariant::Int)) + if(!canConvert(obj["connect-port"], QMetaType::Int)) return false; connectPort = obj["connect-port"].toInt(); @@ -430,7 +432,7 @@ ignorePolicies = false; if(obj.contains("ignore-policies")) { - if(obj["ignore-policies"].type() != QVariant::Bool) + if(typeId(obj["ignore-policies"]) != QMetaType::Bool) return false; ignorePolicies = obj["ignore-policies"].toBool(); @@ -439,7 +441,7 @@ trustConnectHost = false; if(obj.contains("trust-connect-host")) { - if(obj["trust-connect-host"].type() != QVariant::Bool) + if(typeId(obj["trust-connect-host"]) != QMetaType::Bool) return false; trustConnectHost = obj["trust-connect-host"].toBool(); @@ -448,7 +450,7 @@ ignoreTlsErrors = false; if(obj.contains("ignore-tls-errors")) { - if(obj["ignore-tls-errors"].type() != QVariant::Bool) + if(typeId(obj["ignore-tls-errors"]) != QMetaType::Bool) return false; ignoreTlsErrors = obj["ignore-tls-errors"].toBool(); @@ -457,7 +459,7 @@ followRedirects = false; if(obj.contains("follow-redirects")) { - if(obj["follow-redirects"].type() != QVariant::Bool) + if(typeId(obj["follow-redirects"]) != QMetaType::Bool) return false; followRedirects = obj["follow-redirects"].toBool(); @@ -468,16 +470,16 @@ multi = false; if(obj.contains("ext")) { - if(obj["ext"].type() != QVariant::Hash) + if(typeId(obj["ext"]) != QMetaType::QVariantHash) return false; QVariantHash ext = obj["ext"].toHash(); - if(ext.contains("multi") && ext["multi"].type() == QVariant::Bool) + if(ext.contains("multi") && typeId(ext["multi"]) == QMetaType::Bool) { multi = ext["multi"].toBool(); } - if(ext.contains("quiet") && ext["quiet"].type() == QVariant::Bool) + if(ext.contains("quiet") && typeId(ext["quiet"]) == QMetaType::Bool) { quiet = ext["quiet"].toBool(); } diff -Nru pushpin-1.38.0/src/cpp/zhttpresponsepacket.cpp pushpin-1.39.1/src/cpp/zhttpresponsepacket.cpp --- pushpin-1.38.0/src/cpp/zhttpresponsepacket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zhttpresponsepacket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2013 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * $FANOUT_BEGIN_LICENSE:APACHE2$ * @@ -20,6 +21,8 @@ #include "zhttpresponsepacket.h" +#include "qtcompat.h" + QVariant ZhttpResponsePacket::toVariant() const { QVariantHash obj; @@ -120,7 +123,7 @@ bool ZhttpResponsePacket::fromVariant(const QVariant &in) { - if(in.type() != QVariant::Hash) + if(typeId(in) != QMetaType::QVariantHash) return false; QVariantHash obj = in.toHash(); @@ -128,7 +131,7 @@ from.clear(); if(obj.contains("from")) { - if(obj["from"].type() != QVariant::ByteArray) + if(typeId(obj["from"]) != QMetaType::QByteArray) return false; from = obj["from"].toByteArray(); @@ -137,18 +140,18 @@ ids.clear(); if(obj.contains("id")) { - if(obj["id"].type() == QVariant::ByteArray) + if(typeId(obj["id"]) == QMetaType::QByteArray) { Id id; id.id = obj["id"].toByteArray(); ids += id; } - else if(obj["id"].type() == QVariant::List) + else if(typeId(obj["id"]) == QMetaType::QVariantList) { QVariantList vl = obj["id"].toList(); foreach(const QVariant &v, vl) { - if(v.type() != QVariant::Hash) + if(typeId(v) != QMetaType::QVariantHash) return false; Id id; @@ -157,7 +160,7 @@ if(vh.contains("id")) { - if(vh["id"].type() != QVariant::ByteArray) + if(typeId(vh["id"]) != QMetaType::QByteArray) return false; id.id = vh["id"].toByteArray(); @@ -165,7 +168,7 @@ if(vh.contains("seq")) { - if(!vh["seq"].canConvert(QVariant::Int)) + if(!canConvert(vh["seq"], QMetaType::Int)) return false; id.seq = vh["seq"].toInt(); @@ -180,7 +183,7 @@ if(obj.contains("seq")) { - if(!obj["seq"].canConvert(QVariant::Int)) + if(!canConvert(obj["seq"], QMetaType::Int)) return false; if(ids.isEmpty()) @@ -192,7 +195,7 @@ type = Data; if(obj.contains("type")) { - if(obj["type"].type() != QVariant::ByteArray) + if(typeId(obj["type"]) != QMetaType::QByteArray) return false; QByteArray typeStr = obj["type"].toByteArray(); @@ -224,7 +227,7 @@ condition.clear(); if(obj.contains("condition")) { - if(obj["condition"].type() != QVariant::ByteArray) + if(typeId(obj["condition"]) != QMetaType::QByteArray) return false; condition = obj["condition"].toByteArray(); @@ -234,7 +237,7 @@ credits = -1; if(obj.contains("credits")) { - if(!obj["credits"].canConvert(QVariant::Int)) + if(!canConvert(obj["credits"], QMetaType::Int)) return false; credits = obj["credits"].toInt(); @@ -243,7 +246,7 @@ more = false; if(obj.contains("more")) { - if(obj["more"].type() != QVariant::Bool) + if(typeId(obj["more"]) != QMetaType::Bool) return false; more = obj["more"].toBool(); @@ -252,7 +255,7 @@ code = -1; if(obj.contains("code")) { - if(!obj["code"].canConvert(QVariant::Int)) + if(!canConvert(obj["code"], QMetaType::Int)) return false; code = obj["code"].toInt(); @@ -261,7 +264,7 @@ reason.clear(); if(obj.contains("reason")) { - if(obj["reason"].type() != QVariant::ByteArray) + if(typeId(obj["reason"]) != QMetaType::QByteArray) return false; reason = obj["reason"].toByteArray(); @@ -270,7 +273,7 @@ headers.clear(); if(obj.contains("headers")) { - if(obj["headers"].type() != QVariant::List) + if(typeId(obj["headers"]) != QMetaType::QVariantList) return false; foreach(const QVariant &i, obj["headers"].toList()) @@ -279,7 +282,7 @@ if(list.count() != 2) return false; - if(list[0].type() != QVariant::ByteArray || list[1].type() != QVariant::ByteArray) + if(typeId(list[0]) != QMetaType::QByteArray || typeId(list[1]) != QMetaType::QByteArray) return false; headers += HttpHeader(list[0].toByteArray(), list[1].toByteArray()); @@ -289,7 +292,7 @@ body.clear(); if(obj.contains("body")) { - if(obj["body"].type() != QVariant::ByteArray) + if(typeId(obj["body"]) != QMetaType::QByteArray) return false; body = obj["body"].toByteArray(); @@ -298,7 +301,7 @@ contentType.clear(); if(obj.contains("content-type")) { - if(obj["content-type"].type() != QVariant::ByteArray) + if(typeId(obj["content-type"]) != QMetaType::QByteArray) return false; contentType = obj["content-type"].toByteArray(); @@ -309,11 +312,11 @@ multi = false; if(obj.contains("ext")) { - if(obj["ext"].type() != QVariant::Hash) + if(typeId(obj["ext"]) != QMetaType::QVariantHash) return false; QVariantHash ext = obj["ext"].toHash(); - if(ext.contains("multi") && ext["multi"].type() == QVariant::Bool) + if(ext.contains("multi") && typeId(ext["multi"]) == QMetaType::Bool) { multi = ext["multi"].toBool(); } diff -Nru pushpin-1.38.0/src/cpp/zrpcmanager.cpp pushpin-1.39.1/src/cpp/zrpcmanager.cpp --- pushpin-1.38.0/src/cpp/zrpcmanager.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zrpcmanager.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2016 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -56,6 +57,7 @@ }; ZrpcManager *q; + QByteArray instanceId; int ipcFileMode; bool doBind; int timeout; @@ -67,6 +69,8 @@ QZmq::Valve *serverValve; QHash clientReqsById; QList pending; + Connection clientValveConnection; + Connection serverValveConnection; Private(ZrpcManager *_q) : QObject(_q), @@ -88,6 +92,7 @@ bool setupClient() { + clientValveConnection.disconnect(); delete clientValve; delete clientSock; @@ -104,7 +109,7 @@ } clientValve = new QZmq::Valve(clientSock, this); - connect(clientValve, &QZmq::Valve::readyRead, this, &Private::client_readyRead); + clientValveConnection = clientValve->readyRead.connect(boost::bind(&Private::client_readyRead, this, boost::placeholders::_1)); clientValve->open(); @@ -113,6 +118,7 @@ bool setupServer() { + serverValveConnection.disconnect(); delete serverValve; delete serverSock; @@ -129,7 +135,7 @@ } serverValve = new QZmq::Valve(serverSock, this); - connect(serverValve, &QZmq::Valve::readyRead, this, &Private::server_readyRead); + serverValveConnection = serverValve->readyRead.connect(boost::bind(&Private::server_readyRead, this, boost::placeholders::_1)); serverValve->open(); @@ -140,7 +146,10 @@ { assert(clientSock); - QVariant vpacket = packet.toVariant(); + ZrpcRequestPacket p = packet; + p.from = instanceId; + + QVariant vpacket = p.toVariant(); QByteArray buf = TnetString::fromVariant(vpacket); if(log_outputLevel() >= LOG_LEVEL_DEBUG) @@ -166,7 +175,6 @@ serverSock->write(message); } -private slots: void client_readyRead(const QList &message) { if(message.count() != 2) @@ -243,7 +251,7 @@ if(pending.count() >= PENDING_MAX) serverValve->close(); - emit q->requestReady(); + q->requestReady(); } }; @@ -263,6 +271,11 @@ return d->timeout; } +void ZrpcManager::setInstanceId(const QByteArray &instanceId) +{ + d->instanceId = instanceId; +} + void ZrpcManager::setIpcFileMode(int mode) { d->ipcFileMode = mode; diff -Nru pushpin-1.38.0/src/cpp/zrpcmanager.h pushpin-1.39.1/src/cpp/zrpcmanager.h --- pushpin-1.38.0/src/cpp/zrpcmanager.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zrpcmanager.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2014 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -43,6 +43,7 @@ int timeout() const; + void setInstanceId(const QByteArray &instanceId); void setIpcFileMode(int mode); void setBind(bool enable); void setTimeout(int ms); diff -Nru pushpin-1.38.0/src/cpp/zrpcrequest.cpp pushpin-1.39.1/src/cpp/zrpcrequest.cpp --- pushpin-1.38.0/src/cpp/zrpcrequest.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zrpcrequest.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2015 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -38,6 +39,7 @@ ZrpcRequest *q; ZrpcManager *manager; QList reqHeaders; + QByteArray from; QByteArray id; QString method; QVariantHash args; @@ -101,6 +103,7 @@ void handle(const QList &headers, const ZrpcRequestPacket &packet) { reqHeaders = headers; + from = packet.from; id = packet.id; method = packet.method; args = packet.args; @@ -129,7 +132,7 @@ q->onError(); } - emit q->finished(); + q->finished(); } private slots: @@ -141,7 +144,7 @@ condition = ErrorUnavailable; conditionString = "service-unavailable"; cleanup(); - emit q->finished(); + q->finished(); return; } @@ -167,7 +170,7 @@ condition = ErrorTimeout; conditionString = "timeout"; cleanup(); - emit q->finished(); + q->finished(); } }; @@ -186,9 +189,15 @@ ZrpcRequest::~ZrpcRequest() { + destroyed(); delete d; } +QByteArray ZrpcRequest::from() const +{ + return d->from; +} + QByteArray ZrpcRequest::id() const { return d->id; diff -Nru pushpin-1.38.0/src/cpp/zrpcrequest.h pushpin-1.39.1/src/cpp/zrpcrequest.h --- pushpin-1.38.0/src/cpp/zrpcrequest.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zrpcrequest.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2015 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -49,6 +50,7 @@ ZrpcRequest(ZrpcManager *manager, QObject *parent = 0); ~ZrpcRequest(); + QByteArray from() const; QByteArray id() const; QString method() const; QVariantHash args() const; @@ -64,6 +66,7 @@ void setError(ErrorCondition condition, const QVariant &result = QVariant()); Signal finished; + Signal destroyed; protected: virtual void onSuccess(); diff -Nru pushpin-1.38.0/src/cpp/zwebsocket.cpp pushpin-1.39.1/src/cpp/zwebsocket.cpp --- pushpin-1.38.0/src/cpp/zwebsocket.cpp 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/cpp/zwebsocket.cpp 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2014-2023 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -126,11 +126,11 @@ outContentType((int)Frame::Text), multi(false) { - expireTimer = new RTimer(this); + expireTimer = new RTimer; expireTimerConnection = expireTimer->timeout.connect(boost::bind(&Private::expire_timeout, this)); expireTimer->setSingleShot(true); - keepAliveTimer = new RTimer(this); + keepAliveTimer = new RTimer; keppAliveTimerConnection = keepAliveTimer->timeout.connect(boost::bind(&Private::keepAlive_timeout, this)); } @@ -149,7 +149,7 @@ if(expireTimer) { - expireTimer->disconnect(this); + expireTimerConnection.disconnect(); expireTimer->setParent(0); expireTimer->deleteLater(); expireTimer = 0; @@ -157,7 +157,7 @@ if(keepAliveTimer) { - keepAliveTimer->disconnect(this); + keppAliveTimerConnection.disconnect(); keepAliveTimer->setParent(0); keepAliveTimer->deleteLater(); keepAliveTimer = 0; @@ -297,7 +297,7 @@ state = Idle; cleanup(); - QMetaObject::invokeMethod(q, "closed", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "doClosed", Qt::QueuedConnection); } Frame readFrame() @@ -337,7 +337,7 @@ // if peer was already closed, then we're done! state = Idle; cleanup(); - QMetaObject::invokeMethod(q, "closed", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "doClosed", Qt::QueuedConnection); } else { @@ -414,7 +414,7 @@ if(written > 0 || contentBytesWritten > 0) { - emit q->framesWritten(written, contentBytesWritten); + q->framesWritten(written, contentBytesWritten); if(!self) return; } @@ -428,7 +428,7 @@ // if peer was already closed, then we're done! state = Idle; cleanup(); - emit q->closed(); + q->closed(); return; } else @@ -483,7 +483,7 @@ state = Idle; cleanup(); - emit q->error(); + q->error(); return; } else if(packet.type == ZhttpRequestPacket::Cancel) @@ -493,7 +493,7 @@ errorCondition = ErrorGeneric; state = Idle; cleanup(); - emit q->error(); + q->error(); return; } @@ -506,7 +506,7 @@ state = Idle; errorCondition = ErrorGeneric; cleanup(); - emit q->error(); + q->error(); return; } @@ -588,7 +588,7 @@ state = Idle; cleanup(); - emit q->error(); + q->error(); return; } else if(packet.type == ZhttpResponsePacket::Cancel) @@ -598,7 +598,7 @@ errorCondition = ErrorGeneric; state = Idle; cleanup(); - emit q->error(); + q->error(); return; } @@ -614,7 +614,7 @@ state = Idle; errorCondition = ErrorGeneric; cleanup(); - emit q->error(); + q->error(); return; } @@ -640,7 +640,7 @@ errorCondition = ErrorGeneric; cleanup(); log_warning("zws client: error id=%s initial response wrong type", id.data()); - emit q->error(); + q->error(); return; } @@ -650,7 +650,7 @@ errorCondition = ErrorGeneric; cleanup(); log_warning("zws client: error id=%s initial ack did not contain from field", id.data()); - emit q->error(); + q->error(); return; } } @@ -671,7 +671,7 @@ state = Connected; update(); - emit q->connected(); + q->connected(); } else { @@ -740,12 +740,12 @@ { state = Idle; cleanup(); - emit q->closed(); + q->closed(); } else { state = ConnectedPeerClosed; - emit q->peerClosed(); + q->peerClosed(); } } } @@ -977,6 +977,11 @@ } public slots: + void doClosed() + { + q->closed(); + } + void doUpdate() { pendingUpdate = false; @@ -989,14 +994,14 @@ { state = Idle; cleanup(); - emit q->closed(); + q->closed(); return; } else { QPointer self = this; state = ConnectedPeerClosed; - emit q->peerClosed(); + q->peerClosed(); if(!self) return; } @@ -1008,7 +1013,7 @@ readableChanged = false; QPointer self = this; - emit q->readyRead(); + q->readyRead(); if(!self) return; } @@ -1021,7 +1026,7 @@ { state = Idle; errorCondition = ErrorUnavailable; - emit q->error(); + q->error(); cleanup(); return; } @@ -1055,7 +1060,7 @@ { writableChanged = false; - emit q->writeBytesChanged(); + q->writeBytesChanged(); } } } @@ -1066,7 +1071,7 @@ state = Idle; errorCondition = ErrorTimeout; cleanup(); - emit q->error(); + q->error(); } void keepAlive_timeout() diff -Nru pushpin-1.38.0/src/ffi.rs pushpin-1.39.1/src/ffi.rs --- pushpin-1.38.0/src/ffi.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/ffi.rs 2024-03-18 18:42:24.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright (C) 2021-2022 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -32,6 +32,9 @@ use std::ptr; use std::slice; +#[cfg(test)] +use crate::import_cpptest; + #[repr(C)] pub struct ExpiredTimer { key: libc::c_int, @@ -380,6 +383,7 @@ const WZMQ_TCP_KEEPALIVE_IDLE: libc::c_int = 11; const WZMQ_TCP_KEEPALIVE_CNT: libc::c_int = 12; const WZMQ_TCP_KEEPALIVE_INTVL: libc::c_int = 13; +const WZMQ_ROUTER_MANDATORY: libc::c_int = 14; // NOTE: must match values in wzmq.h const WZMQ_DONTWAIT: libc::c_int = 0x01; @@ -722,6 +726,21 @@ return -1; } } + WZMQ_ROUTER_MANDATORY => { + if option_len as u32 != libc::c_int::BITS / 8 { + return -1; + } + + let x = match (option_value as *mut libc::c_int).as_ref() { + Some(x) => x, + None => return -1, + }; + + if let Err(e) = sock.set_router_mandatory(*x != 0) { + set_errno(e.to_raw()); + return -1; + } + } WZMQ_SNDHWM => { if option_len as u32 != libc::c_int::BITS / 8 { return -1; @@ -1091,35 +1110,8 @@ drop(CString::from_raw(c.lib_dir)); } -#[cfg(all(test, target_os = "macos"))] -#[link(name = "pushpin-cpptest")] -#[link(name = "pushpin-cpp")] -#[link(name = "QtCore", kind = "framework")] -#[link(name = "QtNetwork", kind = "framework")] -#[link(name = "QtTest", kind = "framework")] -#[link(name = "c++")] -extern "C" { - pub fn httpheaders_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn jwt_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn routesfile_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn proxyengine_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn jsonpatch_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn instruct_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn idformat_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn publishformat_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn publishitem_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn handlerengine_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; - pub fn template_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; -} - -#[cfg(all(test, not(target_os = "macos")))] -#[link(name = "pushpin-cpptest")] -#[link(name = "pushpin-cpp")] -#[link(name = "Qt5Core")] -#[link(name = "Qt5Network")] -#[link(name = "Qt5Test")] -#[link(name = "stdc++")] -extern "C" { +#[cfg(test)] +import_cpptest! { pub fn httpheaders_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; pub fn jwt_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; pub fn routesfile_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int; diff -Nru pushpin-1.38.0/src/internal.conf pushpin-1.39.1/src/internal.conf --- pushpin-1.38.0/src/internal.conf 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/internal.conf 2024-03-18 18:42:24.000000000 +0000 @@ -41,14 +41,14 @@ # bind DEALER for passing off requests (internal, used with handler) handler_accept_spec=ipc://{rundir}/{ipc_prefix}accept -# bind PULL for receiving retry requests (internal, used with handler) +# bind ROUTER for receiving retry requests (internal, used with handler) handler_retry_in_spec=ipc://{rundir}/{ipc_prefix}retry -# bind PULL for reading handler WS control messages -handler_ws_control_in_spec=ipc://{rundir}/{ipc_prefix}ws-control-in +# list of bind PUSH for sending initial handler WS control messages +handler_ws_control_init_specs=ipc://{rundir}/{ipc_prefix}ws-control-init -# bind PUSH for writing handler WS control messages -handler_ws_control_out_spec=ipc://{rundir}/{ipc_prefix}ws-control-out +# list of bind ROUTER for sending/receiving subsequent handler WS control messages +handler_ws_control_stream_specs=ipc://{rundir}/{ipc_prefix}ws-control-stream # bind PUB for sending stats stats_spec=ipc://{rundir}/{ipc_prefix}proxy-stats @@ -56,34 +56,34 @@ # bind REP for responding to commands command_spec=ipc://{rundir}/{ipc_prefix}proxy-command -# bind PULL for receiving HTTP requests +# list of bind PULL for receiving HTTP requests intreq_in_specs=ipc://{rundir}/{ipc_prefix}intreq-in -# bind ROUTER for continuing HTTP requests +# list of bind ROUTER for continuing HTTP requests intreq_in_stream_specs=ipc://{rundir}/{ipc_prefix}intreq-in-stream -# bind PUB for sending HTTP responses +# list of bind PUB for sending HTTP responses intreq_out_specs=ipc://{rundir}/{ipc_prefix}intreq-out [handler] -# connect REP for responding with inspection info (internal, used with proxy) -proxy_inspect_spec=ipc://{rundir}/{ipc_prefix}inspect +# list of connect REP for responding with inspection info (internal, used with proxy) +proxy_inspect_specs=ipc://{rundir}/{ipc_prefix}inspect -# connect REP for receiving HTTP requests (internal, used with proxy) -proxy_accept_spec=ipc://{rundir}/{ipc_prefix}accept +# list of connect REP for receiving HTTP requests (internal, used with proxy) +proxy_accept_specs=ipc://{rundir}/{ipc_prefix}accept -# connect PUSH for sending HTTP requests (internal, used with proxy) -proxy_retry_out_spec=ipc://{rundir}/{ipc_prefix}retry +# list of connect ROUTER for sending HTTP requests (internal, used with proxy) +proxy_retry_out_specs=ipc://{rundir}/{ipc_prefix}retry -# bind PULL for reading proxy WS control messages -proxy_ws_control_in_spec=ipc://{rundir}/{ipc_prefix}ws-control-out +# list of connect PULL for receiving initial proxy WS control messages +proxy_ws_control_init_specs=ipc://{rundir}/{ipc_prefix}ws-control-init -# bind PUSH for writing proxy WS control messages -proxy_ws_control_out_spec=ipc://{rundir}/{ipc_prefix}ws-control-in +# list of connect ROUTER for sending/receiving subsequent proxy WS control messages +proxy_ws_control_stream_specs=ipc://{rundir}/{ipc_prefix}ws-control-stream -# connect SUB for receiving stats from proxy -proxy_stats_spec=ipc://{rundir}/{ipc_prefix}proxy-stats +# list of connect SUB for receiving stats from proxy +proxy_stats_specs=ipc://{rundir}/{ipc_prefix}proxy-stats # connect DEALER for sending commands to proxy proxy_command_spec=ipc://{rundir}/{ipc_prefix}proxy-command diff -Nru pushpin-1.38.0/src/lib.rs pushpin-1.39.1/src/lib.rs --- pushpin-1.38.0/src/lib.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/lib.rs 2024-03-18 18:42:24.000000000 +0000 @@ -170,7 +170,7 @@ if libc::getpwnam_r( name.as_ptr(), pwd.as_mut_ptr(), - buf.as_mut_ptr() as *mut i8, + buf.as_mut_ptr() as *mut libc::c_char, buf.len(), &mut passwd, ) != 0 @@ -197,7 +197,7 @@ if libc::getgrnam_r( name.as_ptr(), grp.as_mut_ptr(), - buf.as_mut_ptr() as *mut i8, + buf.as_mut_ptr() as *mut libc::c_char, buf.len(), &mut group, ) != 0 @@ -272,6 +272,97 @@ env!("APP_VERSION") } +#[macro_export] +macro_rules! import_cpp { + ($($tt:tt)*) => { + #[link(name = "pushpin-cpp")] + #[cfg_attr( + all(target_os = "macos", qt_lib_prefix = "Qt"), + link(name = "QtCore", kind = "framework"), + link(name = "QtNetwork", kind = "framework") + )] + #[cfg_attr( + all(target_os = "macos", qt_lib_prefix = "Qt6"), + link(name = "Qt6Core", kind = "framework"), + link(name = "Qt6Network", kind = "framework") + )] + #[cfg_attr( + all(target_os = "macos", qt_lib_prefix = "Qt5"), + link(name = "Qt5Core", kind = "framework"), + link(name = "Qt5Network", kind = "framework") + )] + #[cfg_attr( + all(not(target_os = "macos"), qt_lib_prefix = "Qt"), + link(name = "QtCore", kind = "dylib"), + link(name = "QtNetwork", kind = "dylib") + )] + #[cfg_attr( + all(not(target_os = "macos"), qt_lib_prefix = "Qt6"), + link(name = "Qt6Core", kind = "dylib"), + link(name = "Qt6Network", kind = "dylib") + )] + #[cfg_attr( + all(not(target_os = "macos"), qt_lib_prefix = "Qt5"), + link(name = "Qt5Core", kind = "dylib"), + link(name = "Qt5Network", kind = "dylib") + )] + #[cfg_attr(target_os = "macos", link(name = "c++"))] + #[cfg_attr(not(target_os = "macos"), link(name = "stdc++"))] + extern "C" { + $($tt)* + } + }; +} + +#[macro_export] +macro_rules! import_cpptest { + ($($tt:tt)*) => { + #[link(name = "pushpin-cpptest")] + #[link(name = "pushpin-cpp")] + #[cfg_attr( + all(target_os = "macos", qt_lib_prefix = "Qt"), + link(name = "QtCore", kind = "framework"), + link(name = "QtNetwork", kind = "framework"), + link(name = "QtTest", kind = "framework") + )] + #[cfg_attr( + all(target_os = "macos", qt_lib_prefix = "Qt6"), + link(name = "Qt6Core", kind = "framework"), + link(name = "Qt6Network", kind = "framework"), + link(name = "Qt6Test", kind = "framework") + )] + #[cfg_attr( + all(target_os = "macos", qt_lib_prefix = "Qt5"), + link(name = "Qt5Core", kind = "framework"), + link(name = "Qt5Network", kind = "framework"), + link(name = "Qt5Test", kind = "framework") + )] + #[cfg_attr( + all(not(target_os = "macos"), qt_lib_prefix = "Qt"), + link(name = "QtCore", kind = "dylib"), + link(name = "QtNetwork", kind = "dylib"), + link(name = "QtTest", kind = "dylib") + )] + #[cfg_attr( + all(not(target_os = "macos"), qt_lib_prefix = "Qt6"), + link(name = "Qt6Core", kind = "dylib"), + link(name = "Qt6Network", kind = "dylib"), + link(name = "Qt6Test", kind = "dylib") + )] + #[cfg_attr( + all(not(target_os = "macos"), qt_lib_prefix = "Qt5"), + link(name = "Qt5Core", kind = "dylib"), + link(name = "Qt5Network", kind = "dylib"), + link(name = "Qt5Test", kind = "dylib") + )] + #[cfg_attr(target_os = "macos", link(name = "c++"))] + #[cfg_attr(not(target_os = "macos"), link(name = "stdc++"))] + extern "C" { + $($tt)* + } + }; +} + /// # Safety /// /// * `main_fn` must be safe to call. diff -Nru pushpin-1.38.0/src/rust/wzmq.h pushpin-1.39.1/src/rust/wzmq.h --- pushpin-1.38.0/src/rust/wzmq.h 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/rust/wzmq.h 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2024 Fastly, Inc. * * This file is part of Pushpin. * @@ -58,6 +58,7 @@ #define WZMQ_TCP_KEEPALIVE_IDLE 11 #define WZMQ_TCP_KEEPALIVE_CNT 12 #define WZMQ_TCP_KEEPALIVE_INTVL 13 +#define WZMQ_ROUTER_MANDATORY 14 // NOTE: must match values in ffi.rs #define WZMQ_DONTWAIT 0x01 diff -Nru pushpin-1.38.0/src/zhttpsocket.rs pushpin-1.39.1/src/zhttpsocket.rs --- pushpin-1.38.0/src/zhttpsocket.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/zhttpsocket.rs 2024-03-18 18:42:24.000000000 +0000 @@ -272,6 +272,11 @@ handle_index: usize, } +enum SessionAddError { + Full, + Exists, +} + struct SessionDataInner { items: Slab, items_by_key: HashMap, @@ -292,11 +297,15 @@ } } - fn add(&self, key: SessionKey, handle_index: usize) -> Result { + fn add(&self, key: SessionKey, handle_index: usize) -> Result { let inner = &mut *self.inner.lock().unwrap(); - if inner.items.len() == inner.items.capacity() || inner.items_by_key.contains_key(&key) { - return Err(()); + if inner.items.len() == inner.items.capacity() { + return Err(SessionAddError::Full); + } + + if inner.items_by_key.contains_key(&key) { + return Err(SessionAddError::Exists); } let item_key = inner.items.insert(SessionItem { @@ -355,7 +364,7 @@ } } - fn add(&self, key: SessionKey, handle_index: usize) -> Result { + fn add(&self, key: SessionKey, handle_index: usize) -> Result { self.data.add(key, handle_index) } @@ -843,6 +852,8 @@ } } +struct ReqHandlesSendError(MultipartHeader); + struct ServerReqHandles { nodes: Slab>, list: list::List, @@ -918,6 +929,7 @@ } } + // waits until at least one handle is likely writable #[allow(clippy::await_holding_refcell_ref)] async fn check_send(&self) { let mut any_valid = false; @@ -961,9 +973,13 @@ } // non-blocking send. caller should use check_send() first - fn send(&self, header: MultipartHeader, msg: &arena::Arc) { + fn send( + &self, + header: MultipartHeader, + msg: &arena::Arc, + ) -> Result<(), ReqHandlesSendError> { if self.nodes.is_empty() { - return; + return Err(ReqHandlesSendError(header)); } let mut skip = self.send_index.get(); @@ -983,19 +999,30 @@ } } - if let Some(nkey) = selected { - let n = &self.nodes[nkey]; - let p = &n.value; + let nkey = match selected { + Some(nkey) => nkey, + None => return Err(ReqHandlesSendError(header)), + }; + + let n = &self.nodes[nkey]; + let p = &n.value; - match p.pe.sender.try_send((header, arena::Arc::clone(msg))) { - Ok(_) => {} - Err(mpsc::TrySendError::Full(_)) => error!("req sender is full"), - Err(mpsc::TrySendError::Disconnected(_)) => { + if let Err(e) = p.pe.sender.try_send((header, arena::Arc::clone(msg))) { + let header = match e { + mpsc::TrySendError::Full((header, _)) => header, + mpsc::TrySendError::Disconnected((header, _)) => { p.valid.set(false); + self.need_cleanup.set(true); + + header } - } + }; + + return Err(ReqHandlesSendError(header)); } + + Ok(()) } fn need_cleanup(&self) -> bool { @@ -1026,10 +1053,18 @@ } } +enum StreamHandlesSendError { + BadFormat, + NoneReady, + SessionExists, + SessionCapacityFull, +} + struct ServerStreamHandles { nodes: Slab>, list: list::List, recv_scratch: RefCell>, + check_send_any_scratch: RefCell, Session)>>, send_direct_scratch: RefCell>, need_cleanup: Cell, send_index: Cell, @@ -1042,6 +1077,7 @@ nodes: Slab::with_capacity(capacity), list: list::List::default(), recv_scratch: RefCell::new(RecvScratch::new(capacity)), + check_send_any_scratch: RefCell::new(CheckSendScratch::new(capacity)), send_direct_scratch: RefCell::new(Vec::with_capacity(capacity)), need_cleanup: Cell::new(false), send_index: Cell::new(0), @@ -1103,27 +1139,62 @@ } } + // waits until at least one handle is likely writable + #[allow(clippy::await_holding_refcell_ref)] async fn check_send_any(&self) { - let mut at_least_one_writable = false; + let mut any_valid = false; + let mut any_writable = false; for (_, p) in self.list.iter(&self.nodes) { if p.valid.get() { - p.pe.sender_any.wait_writable().await; - at_least_one_writable = true; + any_valid = true; + + if p.pe.sender_any.is_writable() { + any_writable = true; + break; + } } } + if any_writable { + return; + } + // if there are no valid pipes then hang forever. caller can // try again by dropping the future and making a new one - if !at_least_one_writable { + if !any_valid { std::future::pending::<()>().await; } + + // there are valid pipes but none are writable. we'll wait + + let mut scratch = self.check_send_any_scratch.borrow_mut(); + let (mut tasks, slice_scratch) = scratch.get(); + + for (_, p) in self.list.iter(&self.nodes) { + if p.valid.get() { + assert!(tasks.len() < tasks.capacity()); + + tasks.push(p.pe.sender_any.wait_writable()); + } + } + + select_slice(&mut tasks, slice_scratch).await; } // non-blocking send. caller should use check_send_any() first - fn send_any(&self, msg: &arena::Arc, from: &[u8], ids: &[Id<'_>]) { - if self.nodes.is_empty() || ids.is_empty() { - return; + fn send_any( + &self, + msg: &arena::Arc, + from: &[u8], + ids: &[Id], + ) -> Result<(), StreamHandlesSendError> { + if from.len() > FROM_MAX || ids.is_empty() || ids[0].id.len() > REQ_ID_MAX { + return Err(StreamHandlesSendError::BadFormat); + } + + if self.nodes.is_empty() { + return Err(StreamHandlesSendError::NoneReady); } let mut skip = self.send_index.get(); @@ -1143,34 +1214,39 @@ } } - if let Some(nkey) = selected { - let n = &self.nodes[nkey]; - let p = &n.value; + let nkey = match selected { + Some(nkey) => nkey, + None => return Err(StreamHandlesSendError::NoneReady), + }; - let from = match ArrayVec::try_from(from) { - Ok(v) => v, - Err(_) => return, - }; + let n = &self.nodes[nkey]; + let p = &n.value; - let id = match ArrayVec::try_from(ids[0].id) { - Ok(v) => v, - Err(_) => return, - }; + let from = ArrayVec::try_from(from).unwrap(); + let id = ArrayVec::try_from(ids[0].id).unwrap(); - let key = (from, id); + let key = (from, id); - if let Ok(session) = self.sessions.add(key, nkey) { - match p.pe.sender_any.try_send((arena::Arc::clone(msg), session)) { - Ok(_) => {} - Err(mpsc::TrySendError::Full(_)) => error!("stream sender_any is full"), - Err(mpsc::TrySendError::Disconnected(_)) => { - p.valid.set(false); + let session = match self.sessions.add(key, nkey) { + Ok(s) => s, + Err(SessionAddError::Full) => return Err(StreamHandlesSendError::SessionCapacityFull), + Err(SessionAddError::Exists) => return Err(StreamHandlesSendError::SessionExists), + }; - self.need_cleanup.set(true); - } + if let Err(e) = p.pe.sender_any.try_send((arena::Arc::clone(msg), session)) { + match e { + mpsc::TrySendError::Full(_) => {} + mpsc::TrySendError::Disconnected(_) => { + p.valid.set(false); + + self.need_cleanup.set(true); } } + + return Err(StreamHandlesSendError::NoneReady); } + + Ok(()) } #[allow(clippy::await_holding_refcell_ref)] @@ -1599,9 +1675,7 @@ trace!("OUT req {}", packet_to_string(&msg)); } - let h = MultipartHeader::new(); - - req_send = Some(client_req.sock.send_to(h, msg)); + req_send = Some(client_req.sock.send_to(MultipartHeader::new(), msg)); } // req_send Select9::R3(result) => { @@ -1640,8 +1714,7 @@ } // stream_handles_recv_addr Select9::R7((addr, msg)) => { - let mut h = MultipartHeader::new(); - h.push(zmq::Message::from(addr.as_ref())); + let h = vec![zmq::Message::from(addr.as_ref())]; if log_enabled!(log::Level::Trace) { trace!("OUT stream to {}", packet_to_string(&msg)); @@ -1972,7 +2045,10 @@ let messages_memory = Arc::new(arena::SyncMemory::new(arena_size)); - let sessions_max = stream_maxconn + (HANDLES_MAX * handle_bound); + // sessions are created at the time of attempting to send to a handle, so we need enough + // sessions to max out the workers, and max out all the handle channels, and have one + // left to use when attempting to send + let sessions_max = stream_maxconn + (HANDLES_MAX * handle_bound) + 1; let req_sock = AsyncZmqSocket::new(ZmqSocket::new(&ctx, zmq::ROUTER)); @@ -2158,6 +2234,8 @@ trace!("IN server req {}", packet_to_string(&msg)); } + let msg = arena::Arc::new(msg, &messages_memory).unwrap(); + req_in_msg = Some((header, msg)); } Err(e) => error!("server req zmq recv: {}", e), @@ -2179,11 +2257,7 @@ req_send = None; } // req_handles_check_send - Select10::R5(()) => { - let (header, msg) = req_in_msg.take().unwrap(); - - Self::handle_req_message(header, msg, &messages_memory, &req_handles); - } + Select10::R5(()) => Self::handle_req_message(&mut req_in_msg, &req_handles), // stream_in_recv Select10::R6(result) => match result { Ok(msg) => { @@ -2191,6 +2265,8 @@ trace!("IN server stream {}", packet_to_string(&msg)); } + let msg = arena::Arc::new(msg, &messages_memory).unwrap(); + stream_in_msg = Some(msg); } Err(e) => error!("server stream zmq recv: {}", e), @@ -2225,9 +2301,7 @@ } // stream_handles_check_send_any Select10::R10(()) => { - let msg = stream_in_msg.take().unwrap(); - - Self::handle_stream_message_any(msg, &messages_memory, &stream_handles); + Self::handle_stream_message_any(&mut stream_in_msg, &stream_handles); } } @@ -2271,34 +2345,47 @@ } fn handle_req_message( - header: MultipartHeader, - msg: zmq::Message, - messages_memory: &Arc>, + next_msg: &mut Option<(MultipartHeader, arena::Arc)>, handles: &ServerReqHandles, ) { - let msg = arena::Arc::new(msg, messages_memory).unwrap(); + let (header, msg) = next_msg.take().unwrap(); - handles.send(header, &msg); + if let Err(ReqHandlesSendError(header)) = handles.send(header, &msg) { + *next_msg = Some((header, msg)); + } } fn handle_stream_message_any( - msg: zmq::Message, - messages_memory: &Arc>, + next_msg: &mut Option>, handles: &ServerStreamHandles, ) { - let msg = arena::Arc::new(msg, messages_memory).unwrap(); + let msg = next_msg.take().unwrap(); - let mut scratch = ParseScratch::new(); + let ret = { + let mut scratch = ParseScratch::new(); - let (from, ids) = match parse_ids(msg.get(), &mut scratch) { - Ok(ret) => ret, - Err(e) => { - warn!("unable to determine packet id(s): {}", e); - return; - } + let (from, ids) = match parse_ids(msg.get(), &mut scratch) { + Ok(ret) => ret, + Err(e) => { + warn!("unable to determine packet id(s): {}", e); + return; + } + }; + + handles.send_any(&msg, from, ids) }; - handles.send_any(&msg, from, ids); + match ret { + Ok(()) => {} + Err(StreamHandlesSendError::BadFormat) => warn!("stream send_any: bad format"), + Err(StreamHandlesSendError::NoneReady) => *next_msg = Some(msg), + Err(StreamHandlesSendError::SessionExists) => { + warn!("stream send_any: session id in use") + } + Err(StreamHandlesSendError::SessionCapacityFull) => { + error!("stream send_any: session capacity full") + } + } } async fn handle_stream_message_direct( diff -Nru pushpin-1.38.0/src/zmq.rs pushpin-1.39.1/src/zmq.rs --- pushpin-1.38.0/src/zmq.rs 2024-01-09 15:32:59.000000000 +0000 +++ pushpin-1.39.1/src/zmq.rs 2024-03-18 18:42:24.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2020-2023 Fanout, Inc. + * Copyright (C) 2024 Fastly, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -127,7 +128,7 @@ } } -pub type MultipartHeader = ArrayVec; +pub type MultipartHeader = Vec; pub struct ZmqSocket { inner: zmq::Socket, @@ -184,6 +185,10 @@ content: zmq::Message, flags: i32, ) -> Result<(), zmq::Error> { + if header.len() > MULTIPART_HEADERS_MAX { + return Err(zmq::Error::EINVAL); + } + let mut headers: ArrayVec<&[u8], MULTIPART_HEADERS_MAX> = ArrayVec::new(); for part in header { @@ -242,7 +247,7 @@ break; } - if header.len() == header.capacity() { + if header.len() == MULTIPART_HEADERS_MAX { // header too large let flags = 0;