Есть функция, которая ходит во внешний источник и возвращает строку. Вызов
занимает около 20 секунд. Полученный результат можно считать свежим в течение
3 минут. Нужна обёртка, которая всегда отдаёт свежий результат, но не дёргает
источник на каждое обращение.
// NewCached оборачивает fetch. Результат живёт ttl с момента получения.
// now — часы; nil означает time.Now.
func NewCached(fetch func() (string, error), ttl time.Duration, now func() time.Time) *Cached
// Get возвращает свежее значение, при необходимости сходив в источник.
func (c *Cached) Get() (string, error)
Требования
- Пока значение свежее,
Get возвращает его без вызова fetch.
- Значение свежее, пока
now() < момент получения + ttl. Момент получения —
когда fetch вернул результат, а не когда его вызвали: 20 секунд
ожидания не должны съедать время жизни.
- Если значения нет или оно протухло,
Get вызывает fetch.
- Конкурентные
Get, пришедшие, пока идёт запрос в источник, не запускают
второй запрос: они ждут текущий и получают его результат (в том числе ошибку).
- Ошибка
fetch возвращается вызывающим и не кешируется: следующий Get
снова пойдёт в источник.
- Время берётся только из
now. Код проходит go test -race.
Пример
c := NewCached(callSlowAPI, 3*time.Minute, nil)
v, err := c.Get() // ~20 с, сходили в источник
v, err = c.Get() // мгновенно, из кеша
// через 3 минуты после получения — снова в источник
func (c *Cached) GetContext(ctx context.Context) (string, error)
- То же, что
Get, но если ctx отменили, пока вызывающий ждёт источник,
GetContext сразу возвращает ctx.Err().
- Сам запрос в источник при этом не прерывается: его результат нужен
остальным ждущим и попадает в кеш как обычно.
- Свежее значение из кеша возвращается без ожидания.
Устно: почему fetch не принимает контекст вызывающего? Что делать, если
источник лежит, а в кеше есть протухшее значение?
Разбор и эталонное решение — после своей попытки: на собеседовании подсказок не будет.
Сначала попробуй сам — подсказки не дают готовый ответ.