All Go runtime tuning (GOMEMLIMIT, GOGC, GOMAXPROCS) caused more harm
than good — GC thrashing increased both CPU usage and memory consumption.
Go's default GC with GODEBUG=madvdontneed=1 is already optimal for
sing-box on OpenWrt. sing-box uses ~380MB RSS with 130+ outbounds on
a 2GB device, which is normal and expected.
Removed:
- GOMEMLIMIT (caused GC thrashing, 91% CPU)
- GOGC (aggressive values caused CPU spikes)
- GOMAXPROCS (unnecessary, Go handles this)
- mem_scale file (no longer needed)
- Adaptive memory scaling logic
Kept: GODEBUG=madvdontneed=1 only.
rtk git push origin main
- GOMEMLIMIT: remove 256MB RAM threshold cap. Now applies to ALL
devices. Formula: min(35% RAM, 512MB). mem_scale respected.
Fixes sing-box eating 500MB+ on 2GB routers with no limit.
- Cascade delete: when a routing section is deleted, child UCI entries
(subscription_url, urltest, priority_group, priority_level,
section_interface) are now removed. Prevents orphaned entries that
corrupt config generation.
- Migration cleanup: orphan_section_interfaces now cleans ALL child
types, not just section_interface.
- Watchdog logread: kill orphans in stop_runtime(), add SIGTERM/SIGINT
handlers in uloop mode (previously only in fallback mode).
- priority.uc: replace 1s tight sleep loop with dynamic interval based on group config (5s default)
- dns_failover.uc: replace 1s sleep with min(active_interval, recovery_interval)
- watchdog.uc: cap pending_smart_domains at 500 entries, add 300s TTL expiry
- event_controller.uc: batch is_dns_working nslookup calls into single shell process
- byedpi/runtime.uc: add crash-loop backoff (up to 6x base delay after 5 rapid crashes)
- nfqueue/runtime.uc: same crash-loop backoff for nfqws supervisors
- In action.uc: replace broken PCRE non-capturing regex in extract_sing_box_version_from_output with token-based field splitting. ucode POSIX regex engine threw 'Repetition not preceded by valid expression', crashing component_action during binary validation.
- In telegram.uc: replace non-capturing group (?:outbounds|server|servers) with standard capturing group for POSIX regex compatibility.
- In generator_outbounds.uc and servers.uc: enhance is_lx detection to check sing-box-version file in addition to sing-box-variant, ensuring AWG 2.0 root schema is generated even if variant file is desynced.
- In runtime.uc: add fallback recovery in sing_box_check retry loop for endpoints (auto-flattening amnezia sub-objects) and outbounds unknown fields.
- In benchmark.uc and service_check.uc, pass -timeout=2 -type=a to nslookup
- Prevents BusyBox nslookup from sending simultaneous A and AAAA queries across WAN, which causes a 2.5-second retransmit timeout bug on dual-stack/IPv4 routers
- Reduces measured latency from ~2534ms down to true round-trip ping time (~8-15ms)
- Cache tool lookups in has_tool() to avoid spawning redundant child processes
- Remove 'uci: { tachyon: true }' dependency from menu.d/luci-app-tachyon.json so LuCI does not hide the menu node if UCI config is uncommitted/reloading
- Add portable timeout normalization in install.sh ucode to handle Busybox timeout and systems without timeout applet
- Ensure luci_postinst is called directly in IPK postinst (build.sh) and after install in install.sh
- Restart uhttpd alongside rpcd and purge LuCI caches in post-install and uninstaller
- Fix uninstall.sh to remove packages individually with lock retry and omit non-existent luci-i18n-tachyon-en
- In install_ui_packages, verify that menu.d/luci-app-tachyon.json actually exists on disk before proceeding
- Pass force_reinstall=true to opkg when installing sing-box-extended packages to prevent opkg from silently skipping file extraction on up-to-date packages after the binary backup was moved
- Ensure chmod 0755 on /usr/bin/sing-box after package extraction and before validation
- Include /lib in LD_LIBRARY_PATH when library_dir is set to prevent dynamic linker failures
- Robustly match 'version <tag>' from binary output in read_sing_box_binary_version and log diagnostic details on failure
- action.uc: sweep stuck flock 1000 processes before tachyon restart, bound service restart with 25s timeout, and prevent worker lockup
- shell/index.ts: add 15-minute general hard timeout for non-Tachyon components to prevent infinite modal spin; allow same-version confirmation for both install and reinstall
- package.uc: remove disruptive uhttpd restart from luci_postinst to prevent active socket drops
- ui.uc: refresh stale PID state before checking value.running in action_ack so dead jobs can always be acknowledged
- initController.ts & sessionJobs.ts: persist handled job IDs across reloads in sessionStorage, eliminate redundant modal popups on page reload, and guard against concurrent reloads with safeReloadPage
- renderUpdateProgressModal.ts: record active job before reload and poll readiness with fast health check instead of blind 5s freeze
- singbox/runtime.uc: add dynamic 4-tier GOMEMLIMIT and GOGC limits based on RAM, enforce /etc/tachyon/mem_scale, and install managed init script across all binary variants
- service/event_controller.uc: guard probe_nftables() with recovery_bypass and native_internet_restored checks to prevent 5-minute network drop deadlock
- service/watchdog.uc: reduce Telegram worker heartbeat timeout from 900s to 300s and add nslookup fallback to Zero-RTT prefetcher
- diagnostics/runtime.uc & service/lifecycle.uc: use direct-IP NTP pool to eliminate circular DNS/TLS deadlocks, and step clock forward on cold boot if RTC desynced
- service/failover.uc & diagnostics/runtime.uc: flush conntrack states on proxy group switch so client connections immediately migrate
- wrap /sw callback data in cb_data() to strictly enforce 64-byte limit and prevent BUTTON_DATA_INVALID API rejections
- sync telegram daemon in lifecycle reload() so LuCI Save & Apply starts/reloads bot automatically without router reboot
- cleanly stop telegram worker in stop_main() to prevent orphaned background polling processes
- reduce interactive curl timeout in tg_request_via from 35s to 12s, preventing 70s worker freeze on proxy stalls
- close open HTML tags on message truncation and add fallback to plain text if Telegram entity parsing fails
- reduce watchdog telegram worker restart cooldown from 3600s to 120s
- fix nftables check in /test to inspect TachyonTable instead of legacy ip tachyon
- add command aliases /server, /servers, /rules, /ai_doctor and fix integer division in /connections pagination
- When users configure plain IP addresses (e.g. 8.8.8.8, 1.1.1.1, 77.88.8.8) with DoH (https in sing-box), sing-box requires a non-empty HTTP path (defaulting to /dns-query) and proper TLS SNI (server_name) matching the provider certificate.
- Added default /dns-query path fallback in server_from_options().
- Added KNOWN_DNS_SERVER_NAMES mapping for Google, Cloudflare, Yandex, Quad9, AdGuard, Mullvad, Control D, AliDNS, OpenDNS, DNS.SB to provide correct TLS SNI when connecting to raw DNS IPs.
- Added comprehensive DoH/DoT unit tests in tests/dns_failover.sh.
On x86/64 the LAN interface is often named 'eth1', 'enp2s0', etc.
in UCI - not 'lan' or 'br-lan'. The two-tier probe both fail,
producing 'Failed to determine the listening IP address' on startup.
Add a third-tier fallback using ubus network.interface dump:
- is_private_ipv4(): RFC-1918 address check
- network_interface_dump_first_private_ipv4(): iterates all UCI
interfaces, skips wan/wan6/loopback, returns first private IPv4
Workaround (no update needed):
uci set tachyon.settings.service_listen_address='192.168.x.x'
uci commit tachyon && /etc/init.d/tachyon restart
A value like 'cloudflare-dns.com/dns-query' is the sing-box DoH
path format. The previous check only caught 'https://' URLs.
dns_server_value_valid() accepts it because it validates only the
host part — so 'cloudflare-dns.com' passed as a valid hostname.
Fallback servers are plain UDP only; any '/' in the value is the
DoH path separator and must be rejected.
Changes:
- validator.uc: add index(value, '/') >= 0 check before the
dns_server_value_valid call
- config_validator_runtime.sh: add regression test for path-style
DoH ('cloudflare-dns.com/dns-query') in dns_fallback_server
Fallback servers must be plain UDP (IP or IP:port). Before this fix a
user could accidentally enter a DoH URL (https://...) as a fallback
server — the validator passed it through, and it silently failed at
runtime because dns_failover treats all fallback entries as UDP.
Changes:
- validator.uc: after main/bootstrap validation, check all
dns_fallback_server values: reject entries containing '://'
(protocol URL scheme) with a clear error, then validate each
with dns_server_value_valid for format correctness
- config_validator_runtime.sh: assert_rejects for a DoH URL in
dns_fallback_server
Fixes Tachyon startup abort when migrated config has no dns_server or
bootstrap_dns_server (forkop/podkop did not require these fields).
Changes:
- migration.uc: add ensure_dns_server_defaults migration that injects
77.88.8.8 as fallback when either dns list is empty
- default config: pre-mark orphan_section_interface_cleanup and
ensure_dns_server_defaults as applied so fresh installs skip them
- tests/config_migration.sh: regression tests for both missing-DNS cases
and stable named-marker assertion for the new migration
- tests/package_contract.sh: enforce that default config ships both new
migration IDs in applied_migrations
- tests/docker_e2e_test.sh: also install luci-app-tachyon and verify
menu.d + acl.d files are present after install (LuCI menu bug class)
Fixes crash-loop introduced in 1.3.13. The function was called on lines
494-495 (stop/disable conflicting forkop/podkop services at startup) but
the alias was never declared, unlike command_success_from_args on line 48.
This caused a 'Type error: left-hand side is not a function' crash in
configure_service(), sending Tachyon into a 75-second crash loop.
Fixes#44
Remove helper functions section_cache_has_outbounds and section_has_source_cache
which called source_json_path (line 513), subscription_cache_is_usable (line 507)
and object_key_count (line 1115) — all defined AFTER their call site at line 334.
ucode has no function hoisting, so these caused 'left-hand side is not a function'.
Inline the logic directly in runtime_cache_missing using only primitives
available before line 334:
- read_json (line 59)
- object_or_empty, fs.stat, source_id (line 251)
- subscription_source_count (line 300)
- TMP_SUBSCRIPTION_FOLDER (line 30)
- for-in loop to check empty object (no object_key_count needed)
Incorrect function name caused a Type error crash in CI:
'left-hand side is not a function'
Also replace for-in early-return trick with object_key_count() which
already exists in this file (see line 1145).
When a new subscription section is added that shares the same URL as an
existing section, the following race occurs:
1. Sing-box config is generated — source cache doesn't exist yet for the
new section, so it is added with zero outbounds; section-cache file is
written (empty outboundMetadata.names).
2. Background subscription download runs — content is identical to the
existing section, so result is 'unchanged' (return 2), updated=0.
3. reload-service is triggered — runtime_cache_missing returns false
because the section-cache file exists, so sing-box is NOT reloaded.
4. Servers from the new section never appear in the UI.
Fix: runtime_cache_missing now also returns true when a section-cache file
exists but contains no outbounds (names={}) while the source subscription
cache is already available in TMP_SUBSCRIPTION_FOLDER. This causes the
next reload cycle to regenerate sing-box config with the actual servers.