short circuit getCandles if a negative cache exists

This commit is contained in:
Emil Lerch 2026-06-30 16:44:15 -07:00
parent a15b9cf471
commit 9699bed480
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -826,6 +826,17 @@ pub const DataService = struct {
/// `opts.force_refresh = true` -> treats cache as stale and fetches.
pub fn getCandles(self: *DataService, symbol: []const u8, opts: FetchOptions) DataError!FetchResult(Candle) {
var s = self.store();
// Negative cache: this symbol is known to have no candle data on
// any provider (e.g. crypto like DOGE-USD that Tiingo can't
// serve). The marker lives in candles_daily.srf - written by the
// permanent-NotFound path below, or synced verbatim from a
// ZFIN_SERVER. Bail before any server sync or provider fetch so
// repeated calls don't re-hit the network every invocation.
// --refresh-data=force (force_refresh) bypasses this to retry.
if (!opts.force_refresh and s.isNegative(symbol, .candles_daily))
return DataError.FetchFailed;
const today = fmt.todayDate(self.io);
// wall-clock required: candle-meta freshness uses a market-aware
@ -3340,6 +3351,32 @@ test "loadLiveQuotes: empty symbol list returns empty without touching the netwo
try std.testing.expectEqual(@as(usize, 0), prices.count());
}
test "getCandles: negative candle cache short-circuits without touching the network" {
const allocator = std.testing.allocator;
const io = std.testing.io;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const dir_path = try tmp.dir.realPathFileAlloc(io, ".", allocator);
defer allocator.free(dir_path);
var svc = DataService.init(io, allocator, .{ .cache_dir = dir_path });
defer svc.deinit();
// Seed a negative candle entry: this symbol has no candle data on
// any provider (the crypto / defunct-ticker case). The marker lives
// in candles_daily.srf; candles_meta.srf is never created.
var s = svc.store();
try s.ensureSymbolDir("DOGE-USD");
s.writeNegative("DOGE-USD", .candles_daily);
// Any server sync or provider fetch would trip this panic guard.
svc.panic_on_network_attempt = true;
// getCandles must recognize the negative entry and fail fast, with
// no network round-trip (no panic).
try std.testing.expectError(DataError.FetchFailed, svc.getCandles("DOGE-USD", .{}));
}
test "DataService store helper creates valid store" {
const allocator = std.testing.allocator;
const config = Config{