Skip to content
Merged
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
13 changes: 11 additions & 2 deletions .github/workflows/android.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
env:
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
CCACHE_COMPILERCHECK: content

steps:
- name: Setup Android NDK R23
Expand All @@ -25,7 +26,7 @@ jobs:
- name: Configure ccache environment
id: ccache
env:
CACHE_KEY: srt-ccache-android-r23-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('CMakeLists.txt', 'cmake_object_lib_support.c', 'apps/**', 'common/**', 'configure', 'configure-data.tcl', 'haicrypt/**', 'scripts/**', 'srtcore/**') }}
CACHE_KEY: srt-ccache-android-r23-content-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('CMakeLists.txt', 'cmake_object_lib_support.c', 'apps/**', 'common/**', 'configure', 'configure-data.tcl', 'haicrypt/**', 'scripts/**', 'srtcore/**') }}
run: |
echo "CCACHE_DIR=$RUNNER_TEMP/ccache" >> "$GITHUB_ENV"
echo "key=$CACHE_KEY" >> "$GITHUB_OUTPUT"
Expand All @@ -34,12 +35,20 @@ jobs:
with:
path: ${{ runner.temp }}/ccache
key: ${{ steps.ccache.outputs.key }}
restore-keys: srt-ccache-android-r23-${{ runner.os }}-${{ runner.arch }}-
restore-keys: srt-ccache-android-r23-content-v1-${{ runner.os }}-${{ runner.arch }}-
- name: Set up ccache
env:
NDK_PATH: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ccache
ccache --max-size=200M
ccache --show-config
compiler=$(readlink -f "$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++")
printf 'NDK compiler path: %s\n' "$compiler"
sha256sum "$compiler"
stat --format='NDK compiler mtime: %y' "$compiler"
"$compiler" --version
ccache --zero-stats
- name: build
run: |
Expand Down
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ When a build can generate files beneath a hashed source glob, compute the key
once from the clean post-checkout tree and reuse that immutable value for both
restore and save. The Android workflow does this because `build-android`
creates dependency and ABI output beneath its hashed `scripts/**` tree.
Android also uses content-based compiler identity because `setup-ndk`
materializes NDK r23 on each runner; ccache's default mtime identity would turn
identical compiler bytes with fresh mtimes into cross-run misses. Its versioned
cache namespace is part of that policy so an older mtime-keyed archive cannot
block the first content-keyed save.

| Workflow | Coverage |
|----------|----------|
Expand Down Expand Up @@ -219,6 +224,10 @@ pairs, register readiness before launching a fast peer, synchronize the worker,
and close every owner explicitly; finite transfers consume their known byte count
instead of using peer shutdown as an end marker. These lifecycle assertions are
required gates and must not be relaxed or retried away.
Connection-timeout tests enforce their upper timing bound against the async
connect-failure callback timestamp, not elapsed time after the waiting thread is
rescheduled, and also assert the epoll result, callback error, socket state, and
rejection reason.

## WHERE TO LOOK

Expand Down
93 changes: 70 additions & 23 deletions test/test_connection_timeout.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#include <chrono>
#include <future>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include <gtest/gtest.h>
#include "test_env.h"
Expand All @@ -19,6 +23,43 @@ typedef int SOCKET;
using namespace std;
using namespace srt_logging;

struct ConnectResult
{
int error_code;
chrono::steady_clock::time_point observed_at;
};

typedef shared_ptr<promise<ConnectResult> > ConnectResultPromise;

static mutex connect_result_mutex;
static map<SRTSOCKET, ConnectResultPromise> connect_result_promises;

static void ObserveConnectResult(SRTSOCKET socket, const ConnectResultPromise& result_promise)
{
lock_guard<mutex> lock(connect_result_mutex);
connect_result_promises[socket] = result_promise;
}

static void ForgetConnectResult(SRTSOCKET socket)
{
lock_guard<mutex> lock(connect_result_mutex);
connect_result_promises.erase(socket);
}

static void RecordConnectResult(void*, SRTSOCKET socket, int error_code, const sockaddr*, int)
{
ConnectResultPromise result_promise;
{
lock_guard<mutex> lock(connect_result_mutex);
map<SRTSOCKET, ConnectResultPromise>::iterator result = connect_result_promises.find(socket);
if (result == connect_result_promises.end())
return;
result_promise = result->second;
connect_result_promises.erase(result);
}
result_promise->set_value(ConnectResult{error_code, chrono::steady_clock::now()});
}

class TestConnectionTimeout
: public ::srt::Test
{
Expand Down Expand Up @@ -117,6 +158,11 @@ TEST_F(TestConnectionTimeout, Nonblocking) {
ASSERT_NE(srt_epoll_add_usock(pollid, client_sock, &epoll_out), SRT_ERROR);

const sockaddr* psa = reinterpret_cast<const sockaddr*>(&m_sa);
ConnectResultPromise connect_result_promise(new promise<ConnectResult>);
future<ConnectResult> connect_result_future = connect_result_promise->get_future();
ASSERT_EQ(srt_connect_callback(client_sock, &RecordConnectResult, NULL), SRT_SUCCESS);
ObserveConnectResult(client_sock, connect_result_promise);
const chrono::steady_clock::time_point connect_start = chrono::steady_clock::now();
ASSERT_NE(srt_connect(client_sock, psa, sizeof m_sa), SRT_ERROR);

// Socket readiness for connection is checked by polling on WRITE allowed sockets.
Expand All @@ -127,33 +173,35 @@ TEST_F(TestConnectionTimeout, Nonblocking) {
int wlen = 2;
SRTSOCKET write[2];

const chrono::steady_clock::time_point chrono_ts_start = chrono::steady_clock::now();
const int epoll_timeout_ms = connection_timeout_ms + 80;
const int epoll_result = srt_epoll_wait(pollid, read, &rlen, write, &wlen, epoll_timeout_ms, 0, 0, 0, 0);
EXPECT_EQ(epoll_result, 2);
if (epoll_result == 2)
{
EXPECT_EQ(rlen, 1);
EXPECT_EQ(read[0], client_sock);
EXPECT_EQ(wlen, 1);
EXPECT_EQ(write[0], client_sock);
EXPECT_EQ(srt_getsockstate(client_sock), SRTS_BROKEN);
EXPECT_EQ(srt_getrejectreason(client_sock), SRT_REJ_TIMEOUT);
}

// Here we check the connection timeout.
// Epoll timeout is set 100 ms greater than socket's TTL
EXPECT_EQ(srt_epoll_wait(pollid, read, &rlen,
write, &wlen,
connection_timeout_ms + 100, // +100 ms
0, 0, 0, 0)
/* Expected return value is 2. We have only 1 socket, but
* sockets with exceptions are returned to both read and write sets.
*/
, 2);
// Check the actual timeout
const chrono::steady_clock::time_point chrono_ts_end = chrono::steady_clock::now();
const auto delta_ms = chrono::duration_cast<chrono::milliseconds>(chrono_ts_end - chrono_ts_start).count();
// Confidence interval border : +/-80 ms
EXPECT_LE(delta_ms, connection_timeout_ms + 80) << "Timeout was: " << delta_ms;
EXPECT_GE(delta_ms, connection_timeout_ms - 80) << "Timeout was: " << delta_ms;

EXPECT_EQ(rlen, 1);
EXPECT_EQ(read[0], client_sock);
EXPECT_EQ(wlen, 1);
EXPECT_EQ(write[0], client_sock);
const future_status callback_status = connect_result_future.wait_for(chrono::milliseconds(epoll_timeout_ms));
EXPECT_EQ(callback_status, future_status::ready);
if (callback_status == future_status::ready)
{
const ConnectResult connect_result = connect_result_future.get();
EXPECT_EQ(connect_result.error_code, SRT_ENOSERVER);
const int64_t delta_ms =
chrono::duration_cast<chrono::milliseconds>(connect_result.observed_at - connect_start).count();
EXPECT_LE(delta_ms, connection_timeout_ms + 80) << "Timeout callback was: " << delta_ms;
EXPECT_GE(delta_ms, connection_timeout_ms - 80) << "Timeout callback was: " << delta_ms;
}
}

EXPECT_EQ(srt_epoll_remove_usock(pollid, client_sock), SRT_SUCCESS);
EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS);
ForgetConnectResult(client_sock);
(void)srt_epoll_release(pollid);
}

Expand Down Expand Up @@ -416,4 +464,3 @@ TEST(TestConnectionAPI, Listen)

srt_cleanup();
}

Loading