LD_PRELOAD-based per-process time shifting for Linux
English | δΈζ
tshift provides an independent time view for a single Linux process via LD_PRELOAD, without modifying the system clock. It is designed for:
- Time replay (incident rehearsal, data replay)
- Logical clock isolation (multi-tenant, test environments)
- Java / Python / microservices time-related logic validation
- Deterministic time in CI / automated tests
Supports RHEL / Ubuntu / Kylin / UOS, amd64 / aarch64, OpenJDK 8 / 11 / 17 / 21 / 25.
- β Non-invasive: never touches system time, no impact on other processes
- β
Lock-free fastpath: C11
stdatomic, no safepoints, no virtual-thread blocking - β
Lazy init: no
__attribute__((constructor)), safe during early JVM boot - β
Zero IO: no reads from
/proc,/sys,/etc(SELinux / kysec friendly) - β
Single offset model:
display = real + offsetβ simple and predictable - β
Timeline switch: runtime switching via
dlsym(tshift_switch_timeline) - β
Monotonic guarantee:
CLOCK_MONOTONICnever regresses, decoupled from switches - β Cross-language support: C, Java, Python (sleep scaling verified via external timing)
make lib # build libtshift.so only (production)
make all # build libtshift.so + tshift launcher
make test # build and run tests (C, Java, Python)# Just anchor and go β no timeline name needed
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-01-01 12:00:00" \
./your_program# Anonymous timeline (positional)
./build/tshift "2025-01-01 12:00:00" date
# Named timeline
./build/tshift -t "replay_2025@2025-01-01 12:00:00" -u replay_2025 java -jar app.jar
# Multiple timelines + rate
./build/tshift -t "replay@2025-01-01 00:00:00,fast@2024-06-01 00:00:00@2" \
-u replay dateexport LD_PRELOAD=/path/to/libtshift.so
export TSHIFT_TIMELINES="replay_2025@2025-01-01 12:00:00"
export TSHIFT=replay_2025
java -jar your-app.jarReplay production traffic at a specific past point in time:
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2024-12-24 15:30:00" \
java -jar incident-replay.jarRun a 2-hour replay in 1 hour of real wall-clock time:
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-06-01@2" \
./your_appAll sleeps, timeouts, and timers fire at 2x speed. A 10-second Thread.sleep(10000) takes only 5 real seconds.
Slow down time-sensitive logic to observe race conditions:
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-01-01@0.5" \
./your_appEverything runs at half speed. Useful for debugging timing-sensitive code.
Stop all time progression β clock_gettime returns the same value, sleeps never return:
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-01-01 12:00:00@0" \
./your_appDefine multiple time periods and switch between them at runtime:
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="january@2025-01-01,june@2025-06-01" \
TSHIFT="january" \
./your_appIn your application code, switch via dlsym:
typedef int (*switch_fn)(const char *);
switch_fn fn = (switch_fn)dlsym(RTLD_DEFAULT, "tshift_switch_timeline");
fn("june"); // instantly switch to June 2025A REAL timeline is automatically appended to every configuration, so you can always switch back to real time:
fn("REAL"); // switch back to real system timeEach tenant container gets its own time view:
# Tenant A: anchored at 2025-01-01
LD_PRELOAD=./libtshift.so \
TSHIFT_TIMELINES="2025-01-01" \
docker run tenant-a
# Tenant B: anchored at 2024-06-01 at 2x speed
LD_PRELOAD=./libtshift.so \
TSHIFT_TIMELINES="2024-06-01@2" \
docker run tenant-bRun tests with a fixed, deterministic time to avoid flaky time-dependent tests:
LD_PRELOAD=./libtshift.so \
TSHIFT_TIMELINES="2025-01-01 12:00:00" \
pytest tests/Define multiple anonymous timelines and switch by _0_, _1_, _2_:
# Three anonymous timelines: 2025-01-01(2x), 2025-06-01, REAL(0.5x)
LD_PRELOAD=./build/libtshift.so \
TSHIFT_TIMELINES="2025-01-01@2,2025-06-01,REAL@0.5" \
./your_appSwitch by name in code:
fn("_0_"); // switch to 2025-01-01@2
fn("_1_"); // switch to 2025-06-01
fn("_2_"); // switch to REAL@0.5How to trigger timeline switches from outside the process?
#include <signal.h>
#include <dlfcn.h>
void handle_sigusr1(int sig) {
(void)sig;
typedef int (*switch_fn)(const char *);
switch_fn fn = (switch_fn)dlsym(RTLD_DEFAULT, "tshift_switch_timeline");
fn("_1_"); // switch to second timeline
}
void handle_sigusr2(int sig) {
(void)sig;
typedef int (*switch_fn)(const char *);
switch_fn fn = (switch_fn)dlsym(RTLD_DEFAULT, "tshift_switch_timeline");
fn("REAL"); // switch back to real time
}
int main() {
signal(SIGUSR1, handle_sigusr1);
signal(SIGUSR2, handle_sigusr2);
// ... business logic
}
// External trigger: kill -SIGUSR1 <pid> switch to _1_
// kill -SIGUSR2 <pid> switch to real time# Expose HTTP endpoint for timeline switching
from http.server import HTTPServer, BaseHTTPRequestHandler
import ctypes
class SwitchHandler(BaseHTTPRequestHandler):
def do_POST(self):
name = self.path.strip('/')
lib = ctypes.CDLL(None)
lib.tshift_switch_timeline(name.encode())
self.send_response(200)
self.end_headers()
self.wfile.write(b"switched to " + name.encode())
# External trigger: curl -X POST http://localhost:9999/_1_# Monitor file changes to trigger switches
while inotifywait -e modify /tmp/tshift_control; do
timeline=$(cat /tmp/tshift_control)
echo "switching to $timeline"
kill -SIGUSR1 $(pgrep -f your_app)
doneAnonymous timelines simplify single-timeline usage without naming:
# Equivalent to: TSHIFT_TIMELINES="replay@2025-01-01" + TSHIFT="replay"
TSHIFT_TIMELINES="2025-01-01"Properties:
- Supports multiple, comma-separated:
TSHIFT_TIMELINES="2025-01-01@2,2025-06-01,REAL" - Auto-named as
_0_,_1_,_2_... switchable by name TSHIFTenv var not required, defaults to_0_- Auto-REAL append rules still apply (anchor_ns=0 skips duplicate)
- Cannot mix with named format (digit or
Ras first char triggers anonymous mode)
| Variable | Required | Description |
|---|---|---|
TSHIFT_TIMELINES |
yes | Comma-separated timeline definitions (named or anonymous) |
TSHIFT |
no | Active timeline name; defaults to first timeline |
TSHIFT_LIB |
no | Hint for launcher to locate libtshift.so |
Named format:
name@ANCHOR[@RATE]
Anonymous format (first character is digit or R):
ANCHOR[@RATE]
name: 1-63 alphanumeric / underscoreANCHOR:YYYY-MM-DD HH:MM:SS,YYYY-MM-DDTHH:MM:SS,YYYY-MM-DD,REAL, or Unix seconds (with optional fraction)RATE: float, default1.0,0to freeze
REAL timeline: automatically appended to every configuration (unless already present). Provides a way to switch back to real system time at runtime.
| Short | Long | Meaning |
|---|---|---|
-t |
--timelines |
Timeline definitions |
-u |
--use |
Use a named timeline |
-v |
--version |
Print version |
-h |
--help |
Show help |
| Class | Syscalls | Behavior |
|---|---|---|
| π΄ A | clock_gettime, gettimeofday, time, ftime |
Return real + offset |
| π B | clock_getres |
Return 1ns resolution |
| π‘ C | nanosleep, clock_nanosleep, setitimer, alarm, pselect |
Scale duration by 1/rate |
| π’ D | clock_settime, settimeofday, adjtimex |
Reject: -1, errno=EPERM |
| βͺ E | clock_getcpuclockid, pthread_getcpuclockid, CPU clocks |
Passthrough |
CLOCK_MONOTONIC, MONOTONIC_RAW, MONOTONIC_COARSE, BOOTTIME:
- No offset applied (fully decoupled from REALTIME)
- Only scaled by
rate - Always monotonically non-decreasing; switch never causes regression
| Language | Time Retrieval | Sleep Scaling | Notes |
|---|---|---|---|
| C | β Fully supported | β Fully supported | Direct syscall interception |
| Java | β Fully supported | β Supported | Thread.sleep() intercepted via clock_nanosleep |
| Python | β Fully supported | β Supported | pselect/syscall interception covers time.sleep() |
When rate != 1, sleep scaling cannot be verified from inside the process because all clocks (including CLOCK_MONOTONIC) are scaled. The measured interval will always equal rate Γ real_elapsed, making in-process self-tests always fail. This is a measurement artifact, not a defect.
Verification must use external timing (e.g., parent process date +%s%N before/after), which the integration tests (run_tests.sh test_group 8) correctly do and confirm sleep scaling works.
See docs/DESIGN.zh.md (Chinese).
- Multi-process: each process loads its own
libtshift.sowith independent state β fully isolated. - Multi-threaded reads: lock-free design; safe under JDK Virtual Threads.
- Timeline switching: switching is thread-safe, but it is recommended to drive switches from a single control thread (e.g., an HTTP Admin worker) to avoid unpredictable behavior from concurrent switches.
make testTest environment requirements are automatically detected:
- Java: requires
javaandjavacin PATH - Python: requires
python3in PATH - C: always available (built-in)
| Category | Tests |
|---|---|
| C Language | Parsing unit tests (11 items), offset correctness, anonymous timeline, continuous/reset mode, multi-timeline selection, time()/gettimeofday()/ftime() forging, monotonic non-regression, rate=2/0.5/0 scaling, CLOCK_REALTIME_COARSE/BOOTTIME/MONOTONIC_RAW/MONOTONIC_COARSE/BOOTTIME_ALARM forging, clock_getres() precision, pselect() scaling, alarm() scaling, setitimer() scaling, clock_nanosleep(ABSTIME) scaling, launcher CLI, D class rejection (clock_settime, settimeofday, adjtimex), timeline runtime switch, 32 threads Γ 10000 iterations concurrent stress test |
| Java | System.currentTimeMillis(), System.nanoTime() monotonicity, Instant.now(), Date(), Thread.sleep() scaling (external timing) |
| Python | time.time(), time.time_ns(), time.localtime(), datetime.datetime.now(), datetime.datetime.utcnow(), time.monotonic() monotonicity, time.sleep() scaling (external timing) |
tshift/
βββ LICENSE MIT License
βββ Makefile Build system
βββ README.md English docs
βββ README.zh.md Chinese docs
βββ prompts.md Development prompts
βββ src/
β βββ tshift.h Public header
β βββ tshift_core.c Core library (LD_PRELOAD)
β βββ tshift.c Launcher
β βββ env_parse.c Env var parsing
βββ tests/
β βββ c/ C language tests
β β βββ helper.c Time-reading helper
β β βββ test_parse.c Parsing unit tests
β β βββ test_concurrent.c Concurrent stress test
β β βββ test_pselect.c pselect interception test
β β βββ test_switch.c Timeline runtime switch test
β βββ java/ Java tests
β β βββ TimeShiftTest.java JVM time retrieval test
β βββ python/ Python tests
β β βββ TimeShiftTest.py Python time retrieval test
β βββ run_tests.sh Integration test script
β βββ stress.sh Stress test script
MIT β see LICENSE.