Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions FptnLib/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class FptnLib(ConanFile):
# libfptn options
"fptn/*:build_only_fptn_lib": True,
"fptn/*:with_gui_client": False,
# PR1A: iOS socket buffer experiment (0 = kernel default).
"fptn/*:ios_socket_buffer_bytes": 0,
}

def requirements(self):
Expand Down
40 changes: 26 additions & 14 deletions FptnLib/src/websocket/WrapperWebsocketClientBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT)
#include <vector>

#include <mach/mach.h>
#include <TargetConditionals.h>

#include <fptn-protocol-lib/https/websocket_client/websocket_client.h>

Expand Down Expand Up @@ -221,22 +222,15 @@ void packet_callback_adapter(fptn::common::network::IPPacketPtr packet,
const auto& data_vec = packet->Data();
const auto* data = data_vec.data();
const auto len = data_vec.size();
const auto packet_count = wrapper->received_packet_count.fetch_add(1) + 1;
const auto total_bytes = wrapper->received_byte_count.fetch_add(len) + len;
wrapper->received_packet_count.fetch_add(1);
wrapper->received_byte_count.fetch_add(len);
wrapper->callback_enter_count.fetch_add(1);
wrapper->callback_byte_count.fetch_add(len);
wrapper->in_packet_callback = true;
if (packet_count == 1 || packet_count % 500 == 0) {
SPDLOG_WARN(
"WebSocket bridge memory rss={}MB footprint={}MB received_packets={} received_bytes={} callback_enter={} callback_exit={}",
current_resident_size_bytes() / 1024 / 1024,
current_phys_footprint_bytes() / 1024 / 1024,
packet_count,
total_bytes,
wrapper->callback_enter_count.load(),
wrapper->callback_exit_count.load()
);
}
// PR1A: removed SPDLOG_WARN + two Mach task_info syscalls that
// fired on packet #1 and every 500th packet. These added
// non-trivial per-batch overhead on the receive hot path.
// Counters above remain for getStatus() reporting.
wrapper->packet_callback(data, len, wrapper->context);
wrapper->in_packet_callback = false;
wrapper->callback_exit_count.fetch_add(1);
Expand Down Expand Up @@ -303,9 +297,16 @@ void client_run_thread(WebsocketClientWrapper* wrapper) {
packet_callback_adapter(std::move(packet), wrapper);
};

// PR1A: io_context concurrency hint of 1 on iOS. The wrapper
// already drives the context from a single native thread; the
// hint only affects internal data-structure sizing.
wrapper->client = std::make_shared<fptn::protocol::https::WebsocketClient>(
std::move(client_config),
#if defined(__APPLE__) && TARGET_OS_IOS
1
#else
4
#endif
);
}

Expand Down Expand Up @@ -463,7 +464,7 @@ bool WebsocketSwiftBridge::isStarted() const {
}

WebsocketClientBridgeStatus WebsocketSwiftBridge::getStatus() const {
WebsocketClientBridgeStatus status = {false, false, 0, "", "", 0, 0, 0, 0, 0, 0, 0, false};
WebsocketClientBridgeStatus status = {false, false, 0, "", "", 0, 0, 0, 0, 0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0, 0};
if (!wrapper_) {
status.last_error = "Invalid handle";
return status;
Expand All @@ -483,6 +484,17 @@ WebsocketClientBridgeStatus WebsocketSwiftBridge::getStatus() const {
status.callback_exit_count = wrapper_->callback_exit_count.load();
status.callback_byte_count = wrapper_->callback_byte_count.load();
status.in_packet_callback = wrapper_->in_packet_callback.load();
// PR1A: socket buffer diagnostics and process-wide lifecycle counters.
if (wrapper_->client) {
status.requested_rcvbuf_bytes = wrapper_->client->GetRequestedRcvbufBytes();
status.requested_sndbuf_bytes = wrapper_->client->GetRequestedSndbufBytes();
status.effective_rcvbuf_bytes = wrapper_->client->GetEffectiveRcvbufBytes();
status.effective_sndbuf_bytes = wrapper_->client->GetEffectiveSndbufBytes();
status.socket_buffer_set_error_count = wrapper_->client->GetSocketBufferSetErrorCount();
}
status.live_clients = fptn::protocol::https::WebsocketClient::GetLiveClients();
status.active_reader_coroutines = fptn::protocol::https::WebsocketClient::GetActiveReaderCoroutines();
status.active_sender_coroutines = fptn::protocol::https::WebsocketClient::GetActiveSenderCoroutines();
} catch (const std::exception& ex) {
status.last_error = ex.what();
} catch (...) {
Expand Down
9 changes: 9 additions & 0 deletions FptnLib/src/websocket/WrapperWebsocketClientBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ struct WebsocketClientBridgeStatus {
int64_t callback_exit_count;
int64_t callback_byte_count;
bool in_packet_callback;
// PR1A: socket buffer diagnostics and process-wide lifecycle counters.
int requested_rcvbuf_bytes;
int requested_sndbuf_bytes;
int effective_rcvbuf_bytes;
int effective_sndbuf_bytes;
int live_clients;
int active_reader_coroutines;
int active_sender_coroutines;
int socket_buffer_set_error_count;
};

// PR0: SWIFT_NONCOPYABLE makes Swift import this as ~Copyable,
Expand Down
19 changes: 18 additions & 1 deletion FptnVPN/Cpp/Wrappers/WebScoketClientBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ struct WebsocketClientStatus: Sendable {
let callbackExitCount: Int64
let callbackByteCount: Int64
let inPacketCallback: Bool
// PR1A: socket buffer diagnostics and process-wide lifecycle counters.
let requestedRcvbufBytes: Int
let requestedSndbufBytes: Int
let effectiveRcvbufBytes: Int
let effectiveSndbufBytes: Int
let liveClients: Int
let activeReaderCoroutines: Int
let activeSenderCoroutines: Int
let socketBufferSetErrorCount: Int
}

final class WebsocketClientBridge {
Expand Down Expand Up @@ -149,7 +158,15 @@ final class WebsocketClientBridge {
callbackEnterCount: Int64(raw.callback_enter_count),
callbackExitCount: Int64(raw.callback_exit_count),
callbackByteCount: Int64(raw.callback_byte_count),
inPacketCallback: raw.in_packet_callback
inPacketCallback: raw.in_packet_callback,
requestedRcvbufBytes: Int(raw.requested_rcvbuf_bytes),
requestedSndbufBytes: Int(raw.requested_sndbuf_bytes),
effectiveRcvbufBytes: Int(raw.effective_rcvbuf_bytes),
effectiveSndbufBytes: Int(raw.effective_sndbuf_bytes),
liveClients: Int(raw.live_clients),
activeReaderCoroutines: Int(raw.active_reader_coroutines),
activeSenderCoroutines: Int(raw.active_sender_coroutines),
socketBufferSetErrorCount: Int(raw.socket_buffer_set_error_count)
)
}
}
19 changes: 18 additions & 1 deletion FptnVPNTunnel/Cpp/WebScoketClientBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ struct WebsocketClientStatus: Sendable {
let callbackExitCount: Int64
let callbackByteCount: Int64
let inPacketCallback: Bool
// PR1A: socket buffer diagnostics and process-wide lifecycle counters.
let requestedRcvbufBytes: Int
let requestedSndbufBytes: Int
let effectiveRcvbufBytes: Int
let effectiveSndbufBytes: Int
let liveClients: Int
let activeReaderCoroutines: Int
let activeSenderCoroutines: Int
let socketBufferSetErrorCount: Int
}

final class WebsocketClientBridge {
Expand Down Expand Up @@ -162,7 +171,15 @@ final class WebsocketClientBridge {
callbackEnterCount: Int64(raw.callback_enter_count),
callbackExitCount: Int64(raw.callback_exit_count),
callbackByteCount: Int64(raw.callback_byte_count),
inPacketCallback: raw.in_packet_callback
inPacketCallback: raw.in_packet_callback,
requestedRcvbufBytes: Int(raw.requested_rcvbuf_bytes),
requestedSndbufBytes: Int(raw.requested_sndbuf_bytes),
effectiveRcvbufBytes: Int(raw.effective_rcvbuf_bytes),
effectiveSndbufBytes: Int(raw.effective_sndbuf_bytes),
liveClients: Int(raw.live_clients),
activeReaderCoroutines: Int(raw.active_reader_coroutines),
activeSenderCoroutines: Int(raw.active_sender_coroutines),
socketBufferSetErrorCount: Int(raw.socket_buffer_set_error_count)
)
}
}
Expand Down
19 changes: 16 additions & 3 deletions build_fptn_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ resolve_build_type() {

BUILD_TYPE="$(resolve_build_type)"

# PR1A: iOS socket buffer experiment parameter.
# 0 = kernel default. Override: FPTN_IOS_SOCKET_BUFFER_BYTES=262144
SOCKET_BUFFER_BYTES="${FPTN_IOS_SOCKET_BUFFER_BYTES:-0}"
case "$SOCKET_BUFFER_BYTES" in
0|262144|524288) ;;
*)
echo "error: invalid FPTN_IOS_SOCKET_BUFFER_BYTES=$SOCKET_BUFFER_BYTES (expected 0, 262144, or 524288)" >&2
exit 1
;;
esac

resolve_framework_binary() {
local framework_path="$1"
if [ -f "${framework_path}/Versions/1.0.0/fptn_native_lib" ]; then
Expand Down Expand Up @@ -267,6 +278,7 @@ if [ "${FPTN_NATIVE_BUILD_IF_MISSING:-0}" = "1" ]; then
grep -q "\"wrapper_hash\": \"${current_wrapper_hash}\"" "$manifest_path" 2>/dev/null || return 1
grep -q "\"build_hash\": \"${current_build_hash}\"" "$manifest_path" 2>/dev/null || return 1
grep -Fq "\"compiler\": \"${current_compiler_id}\"" "$manifest_path" 2>/dev/null || return 1
grep -q "\"ios_socket_buffer_bytes\": ${SOCKET_BUFFER_BYTES}" "$manifest_path" 2>/dev/null || return 1
return 0
}

Expand All @@ -285,7 +297,7 @@ if [ "$TARGET" = "macos" ]; then
# ── arm64 slice ──────────────────────────────────────────────────────────
ARM64_DIR="build-macos-${BUILD_TYPE}"
echo "Building arm64 slice (${BUILD_TYPE})..."
conan install . --profile:host="conan-macos-profile" --profile:build=conan-macos-profile --build=missing --output-folder="$ARM64_DIR" -s build_type="$BUILD_TYPE"
conan install . --profile:host="conan-macos-profile" --profile:build=conan-macos-profile --build=missing --output-folder="$ARM64_DIR" -s build_type="$BUILD_TYPE" -o "fptn/*:ios_socket_buffer_bytes=${SOCKET_BUFFER_BYTES}"
cd "$ARM64_DIR"
cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/${BUILD_TYPE}/generators/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
Expand All @@ -297,7 +309,7 @@ if [ "$TARGET" = "macos" ]; then
# ── x86_64 slice ─────────────────────────────────────────────────────────
X86_DIR="build-macos-x86_64-${BUILD_TYPE}"
echo "Building x86_64 slice (${BUILD_TYPE})..."
conan install . --profile:host="conan-macos-x86_64-profile" --profile:build=conan-macos-profile --build=missing --output-folder="$X86_DIR" -s build_type="$BUILD_TYPE"
conan install . --profile:host="conan-macos-x86_64-profile" --profile:build=conan-macos-profile --build=missing --output-folder="$X86_DIR" -s build_type="$BUILD_TYPE" -o "fptn/*:ios_socket_buffer_bytes=${SOCKET_BUFFER_BYTES}"
cd "$X86_DIR"
cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/${BUILD_TYPE}/generators/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
Expand Down Expand Up @@ -354,7 +366,7 @@ if [ "$TARGET" = "macos" ]; then
exit 0
fi

conan install . --profile:host="$HOST_PROFILE" --profile:build=conan-macos-profile --build=missing --output-folder="$OUTPUT_DIR" -s build_type="$BUILD_TYPE"
conan install . --profile:host="$HOST_PROFILE" --profile:build=conan-macos-profile --build=missing --output-folder="$OUTPUT_DIR" -s build_type="$BUILD_TYPE" -o "fptn/*:ios_socket_buffer_bytes=${SOCKET_BUFFER_BYTES}"

cd "$OUTPUT_DIR"
cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/${BUILD_TYPE}/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE"
Expand Down Expand Up @@ -408,6 +420,7 @@ copy_framework_to_dest() {
"wrapper_hash": "${wrapper_hash}",
"build_hash": "${build_hash}",
"compiler": "${compiler_id}",
"ios_socket_buffer_bytes": ${SOCKET_BUFFER_BYTES},
"build_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
MANIFEST
Expand Down
Loading