Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions internal/storage/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Storage struct {
done chan struct{}
gcInterval time.Duration
mux sync.RWMutex
closeOnce sync.Once
}

// Entry represents a value stored in memory along with its expiration.
Expand All @@ -38,7 +39,7 @@ func New(config ...Config) *Storage {
store := &Storage{
db: make(map[string]Entry),
gcInterval: cfg.GCInterval,
done: make(chan struct{}),
done: make(chan struct{}, 1),
Comment thread
gaby marked this conversation as resolved.
}

// Start garbage collector
Expand Down Expand Up @@ -150,7 +151,12 @@ func (s *Storage) ResetWithContext(ctx context.Context) error {
// Close stops the background garbage collector and releases resources
// associated with the storage instance.
func (s *Storage) Close() error {
s.done <- struct{}{}
s.closeOnce.Do(func() {
select {
case s.done <- struct{}{}:
default:
}
})
Comment thread
gaby marked this conversation as resolved.
return nil
}

Expand Down
19 changes: 19 additions & 0 deletions internal/storage/memory/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,25 @@ func Test_Storage_Memory_Close(t *testing.T) {
require.NoError(t, testStore.Close())
}

func Test_Storage_Memory_Close_Idempotent(t *testing.T) {
t.Parallel()

testStore := New()
require.NoError(t, testStore.Close())

errCh := make(chan error, 1)
go func() {
errCh <- testStore.Close()
}()

select {
case err := <-errCh:
require.NoError(t, err)
case <-time.After(time.Second):
t.Fatal("second Close blocked")
}
}

func Test_Storage_Memory_Conn(t *testing.T) {
t.Parallel()
testStore := New()
Expand Down
26 changes: 4 additions & 22 deletions middleware/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"sort"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/gofiber/utils/v2"
Expand All @@ -24,11 +23,6 @@ import (
"github.com/gofiber/fiber/v3"
)

// timestampUpdatePeriod is the period which is used to check the cache expiration.
// It should not be too long to provide more or less acceptable expiration error, and in the same
// time it should not be too short to avoid overwhelming of the system
const timestampUpdatePeriod = 300 * time.Millisecond

// buffer size for hexpool
const (
hexLen = sha256.Size * 2
Expand Down Expand Up @@ -148,11 +142,8 @@ func New(config ...Config) fiber.Handler {
}
}

var (
// Cache settings
mux = &sync.RWMutex{}
timestamp = safeUnixSeconds(time.Now())
)
// Cache settings
mux := &sync.RWMutex{}
// Create manager to simplify storage operations ( see manager.go )
manager := newManager(cfg.Storage, redactKeys)
// Create indexed heap for tracking expirations ( see heap.go )
Expand All @@ -169,15 +160,6 @@ func New(config ...Config) fiber.Handler {
hashAuthorization := makeHashAuthFunc(hexBufPool)
buildVaryKey := makeBuildVaryKeyFunc(hexBufPool)

// Update timestamp in the configured interval
go func() {
ticker := time.NewTicker(timestampUpdatePeriod)
defer ticker.Stop()
for range ticker.C {
atomic.StoreUint64(&timestamp, safeUnixSeconds(time.Now()))
}
}()

// Delete key from both manager and storage
deleteKey := func(ctx context.Context, dkey string) error {
if err := manager.del(ctx, dkey); err != nil {
Expand Down Expand Up @@ -325,7 +307,7 @@ func New(config ...Config) fiber.Handler {
}
}
// Get timestamp
ts := atomic.LoadUint64(&timestamp)
ts := safeUnixSeconds(time.Now())
Comment thread
gaby marked this conversation as resolved.
Outdated
Comment thread
gaby marked this conversation as resolved.
Outdated

// Cache Entry found
if e != nil {
Expand Down Expand Up @@ -815,7 +797,7 @@ func New(config ...Config) fiber.Handler {
return nil
}

ts = atomic.LoadUint64(&timestamp)
ts = safeUnixSeconds(time.Now())
responseTS := max(ts, nowUnix)

maxAgeSeconds := uint64(time.Duration(math.MaxInt64) / time.Second)
Expand Down
33 changes: 25 additions & 8 deletions middleware/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,28 @@ func New(config ...Config) fiber.Handler {
// Create correct timeformat
timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat))

// Update date/time every 500 milliseconds in a separate go routine
if strings.Contains(cfg.Format, "${"+TagTime+"}") {
go func() {
for {
time.Sleep(cfg.TimeInterval)
timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat))
timeEnabled := strings.Contains(cfg.Format, "${"+TagTime+"}")
var nextTimestampUpdate atomic.Int64
if timeEnabled {
nextTimestampUpdate.Store(time.Now().Add(cfg.TimeInterval).UnixNano())
}

refreshTimestamp := func(now time.Time) {
if !timeEnabled {
return
}

nowUnixNano := now.UnixNano()
for {
next := nextTimestampUpdate.Load()
if nowUnixNano < next {
return
Comment thread
gaby marked this conversation as resolved.
Outdated
Comment thread
gaby marked this conversation as resolved.
Outdated
}
}()
if nextTimestampUpdate.CompareAndSwap(next, now.Add(cfg.TimeInterval).UnixNano()) {
timestamp.Store(now.In(cfg.timeZoneLocation).Format(cfg.TimeFormat))
Comment thread
gaby marked this conversation as resolved.
Outdated
return
}
Comment thread
gaby marked this conversation as resolved.
Outdated
}
}
// Set PID once
pid := strconv.Itoa(os.Getpid())
Expand Down Expand Up @@ -106,7 +120,10 @@ func New(config ...Config) fiber.Handler {
// no need for a reset, as long as we always override everything
data.Pid = pid
data.ErrPaddingStr = errPaddingStr
data.Timestamp = timestamp
if timeEnabled {
refreshTimestamp(time.Now())
}
data.Timestamp.Store(timestamp.Load())
// These compiled chains are shared across requests. The default logger and
// custom LoggerFunc implementations must only read them, for example via
// logtemplate.ExecuteChains.
Expand Down
33 changes: 33 additions & 0 deletions middleware/logger/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,39 @@ func Test_Logger_Data_Race(t *testing.T) {
require.Equal(t, fiber.StatusOK, resp2.StatusCode)
}

func Test_Logger_TimeUpdatesAfterInterval(t *testing.T) {
t.Parallel()

var buf bytes.Buffer

app := fiber.New()
app.Use(New(Config{
Format: "${time}",
TimeFormat: time.RFC3339Nano,
TimeInterval: 10 * time.Millisecond,
Stream: &buf,
}))
app.Get("/", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent)
})

resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", http.NoBody))
require.NoError(t, err)
require.Equal(t, fiber.StatusNoContent, resp.StatusCode)
first := buf.String()
require.NotEmpty(t, first)

buf.Reset()
time.Sleep(20 * time.Millisecond)

resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", http.NoBody))
require.NoError(t, err)
require.Equal(t, fiber.StatusNoContent, resp.StatusCode)
second := buf.String()
require.NotEmpty(t, second)
require.NotEqual(t, first, second)
}
Comment thread
gaby marked this conversation as resolved.
Outdated
Comment thread
gaby marked this conversation as resolved.
Outdated

// go test -run Test_Response_Header
func Test_Response_Header(t *testing.T) {
t.Parallel()
Expand Down
Loading