diff --git a/Build/build-chn-cidr.ts b/Build/build-chn-cidr.ts index eb234d13..598963c7 100644 --- a/Build/build-chn-cidr.ts +++ b/Build/build-chn-cidr.ts @@ -2,21 +2,38 @@ import { fetchRemoteTextByLine } from './lib/fetch-text-by-line'; import { processLineFromReadline } from './lib/process-line'; import { task } from './trace'; -import { exclude } from 'fast-cidr-tools'; +import { contains, exclude } from 'fast-cidr-tools'; import { createMemoizedPromise } from './lib/memo-promise'; import { CN_CIDR_NOT_INCLUDED_IN_CHNROUTE, NON_CN_CIDR_INCLUDED_IN_CHNROUTE } from './constants/cidr'; import { appendArrayInPlace } from './lib/append-array-in-place'; import { IPListOutput } from './lib/create-file'; +import { cachedOnlyFail } from './lib/fs-memo'; -export const getChnCidrPromise = createMemoizedPromise(async () => { - const [cidr4, cidr6] = await Promise.all([ - fetchRemoteTextByLine('https://raw.githubusercontent.com/misakaio/chnroutes2/master/chnroutes.txt').then(processLineFromReadline), - fetchRemoteTextByLine('https://gaoyifan.github.io/china-operator-ip/china6.txt').then(processLineFromReadline) - ]); +const PROBE_CHN_CIDR = [ + // NetEase Hangzhou + '223.252.196.38' +]; - appendArrayInPlace(cidr4, CN_CIDR_NOT_INCLUDED_IN_CHNROUTE); - return [exclude(cidr4, NON_CN_CIDR_INCLUDED_IN_CHNROUTE, true), cidr6] as const; -}); +export const getChnCidrPromise = createMemoizedPromise(cachedOnlyFail( + async function getChnCidr() { + const [cidr4, cidr6] = await Promise.all([ + fetchRemoteTextByLine('https://raw.githubusercontent.com/misakaio/chnroutes2/master/chnroutes.txt').then(processLineFromReadline), + fetchRemoteTextByLine('https://gaoyifan.github.io/china-operator-ip/china6.txt').then(processLineFromReadline) + ]); + + appendArrayInPlace(cidr4, CN_CIDR_NOT_INCLUDED_IN_CHNROUTE); + + if (!contains(cidr4, PROBE_CHN_CIDR)) { + throw new TypeError('chnroutes missing probe IP'); + } + + return [exclude(cidr4, NON_CN_CIDR_INCLUDED_IN_CHNROUTE, true), cidr6] as const; + }, + { + serializer: JSON.stringify, + deserializer: JSON.parse + } +)); export const buildChnCidr = task(require.main === module, __filename)(async (span) => { const [filteredCidr4, cidr6] = await span.traceChildAsync('download chnroutes2', getChnCidrPromise); diff --git a/Build/constants/cidr.ts b/Build/constants/cidr.ts index b6b561cf..db43c8ee 100644 --- a/Build/constants/cidr.ts +++ b/Build/constants/cidr.ts @@ -55,5 +55,8 @@ export const CN_CIDR_NOT_INCLUDED_IN_CHNROUTE = [ '139.219.0.0/16', // AS58593, Azure China, Shanghai '143.64.0.0/16', // AS58593, Azure China, Beijing '159.27.0.0/16', // AS58593, Azure China, Beijing - '163.228.0.0/16' // AS58593, Azure China, Beijing + '163.228.0.0/16', // AS58593, Azure China, Beijing + + // NetEase + '223.252.196.0/24' ]; diff --git a/Build/lib/fs-memo.ts b/Build/lib/fs-memo.ts index 13c14a55..828203c0 100644 --- a/Build/lib/fs-memo.ts +++ b/Build/lib/fs-memo.ts @@ -62,9 +62,7 @@ export function cache( const cacheName = fn.name || fixedKey; - const { temporaryBypass, incrementTtlWhenHit } = opt; - - if (temporaryBypass) { + if (opt.temporaryBypass) { return fn(...args); } @@ -82,11 +80,54 @@ export function cache( console.log(picocolors.green('[cache] hit'), picocolors.gray(cacheName || cacheKey)); - if (incrementTtlWhenHit) { - fsMemoCache.updateTtl(cacheKey, TTL); - } + fsMemoCache.updateTtl(cacheKey, TTL); const deserializer = 'deserializer' in opt ? opt.deserializer : identity as any; return deserializer(cached); }; } + +export function cachedOnlyFail( + fn: (...args: Args) => Promise, + opt: FsMemoCacheOptions +): (...args: Args) => Promise { + const fixedKey = fn.toString(); + + return async function cachedCb(...args: Args) { + // Construct the complete cache key for this function invocation + // typeson.stringify is still limited. For now we uses typescript to guard the args. + const cacheKey = (await Promise.all([ + xxhash64(fixedKey), + xxhash64(typeson.stringifySync(args)) + ])).join('|'); + + const cacheName = fn.name || fixedKey; + + if (opt.temporaryBypass) { + return fn(...args); + } + + const cached = fsMemoCache.get(cacheKey); + + try { + const value = await fn(...args); + + const serializer = 'serializer' in opt ? opt.serializer : identity as any; + fsMemoCache.set(cacheKey, serializer(value), TTL); + + return value; + } catch (e) { + if (cached == null) { + console.log(picocolors.red('[fail] and no cache, throwing'), picocolors.gray(cacheName || cacheKey)); + throw e; + } + + fsMemoCache.updateTtl(cacheKey, TTL); + + console.log(picocolors.yellow('[fail] try cache'), picocolors.gray(cacheName || cacheKey)); + const deserializer = 'deserializer' in opt ? opt.deserializer : identity as any; + + return deserializer(cached); + } + }; +}