templating: Rework timers

This commit is contained in:
Tom Wiesing 2023-01-31 12:51:54 +01:00
parent 66eb13df30
commit 2d163a4dad
No known key found for this signature in database
9 changed files with 232 additions and 46 deletions

View file

@ -3,6 +3,8 @@ package cancel
import (
"context"
"time"
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
)
// Anyways behaves like context.WithTimeout, except that if the Done() channel of ctx is closed before Anyways is called, the returned context's Done() channel is only closed after timeout.
@ -22,8 +24,9 @@ func Anyways(ctx context.Context, timeout time.Duration) (context.Context, conte
// start waiting for the timer (or the cancel to be called)
finish := make(chan struct{})
go func() {
t := time.NewTimer(timeout)
defer t.Stop()
t := timex.NewTimer()
t.Reset(timeout)
defer timex.ReleaseTimer(t)
defer close(any.done)

View file

@ -3,10 +3,14 @@ package httpx
import (
"html/template"
"net/http"
"time"
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
"github.com/rs/zerolog"
)
const HTMLFlushInterval = time.Second / 10
// WriteHTML writes a html response of type T to w.
// If an error occured, writes an error response instead.
func WriteHTML[T any](result T, err error, template *template.Template, templateName string, w http.ResponseWriter, r *http.Request) (e error) {
@ -17,17 +21,39 @@ func WriteHTML[T any](result T, err error, template *template.Template, template
}
}()
// create a synced respone writer
sw := &SyncedResponseWriter{ResponseWriter: w}
done := make(chan struct{})
defer close(done)
// and regularly flush it until the end of the function
go func() {
timer := timex.NewTimer()
defer timex.ReleaseTimer(timer)
for {
timer.Reset(HTMLFlushInterval)
select {
case <-timer.C:
sw.Flush()
case <-done:
return
}
}
}()
// intercept any errors
if HTMLInterceptor.Intercept(w, r, err) {
if HTMLInterceptor.Intercept(sw, r, err) {
return nil
}
// write out the response as html
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
sw.Header().Set("Content-Type", "text/html")
sw.WriteHeader(http.StatusOK)
// minify html!
minifier := MinifyHTMLWriter(w)
minifier := MinifyHTMLWriter(sw)
defer minifier.Close()
// and return the template

47
pkg/httpx/sync.go Normal file
View file

@ -0,0 +1,47 @@
package httpx
import (
"net/http"
"sync"
)
// SyncedResponseWriter wraps a http ResponseWriter to syncronize all actions
type SyncedResponseWriter struct {
m sync.Mutex
http.ResponseWriter
}
func (rw *SyncedResponseWriter) Header() http.Header {
rw.m.Lock()
defer rw.m.Unlock()
return rw.ResponseWriter.Header()
}
func (rw *SyncedResponseWriter) Write(data []byte) (int, error) {
rw.m.Lock()
defer rw.m.Unlock()
return rw.ResponseWriter.Write(data)
}
func (rw *SyncedResponseWriter) WriteHeader(statusCode int) {
rw.m.Lock()
defer rw.m.Unlock()
rw.ResponseWriter.WriteHeader(statusCode)
}
// Flush flushes any partial output to the underlying ResponseWriter.
// If the wrapped ResponseWriter does not implement flush, the function performs no operation.
func (rw *SyncedResponseWriter) Flush() {
f, ok := rw.ResponseWriter.(http.Flusher)
if !ok {
return
}
rw.m.Lock()
defer rw.m.Unlock()
f.Flush()
}

View file

@ -3,18 +3,28 @@ package timex
import (
"context"
"sync"
"time"
)
// NewTimer creates a new timer with undefined interval.
// The timer is stopped.
func NewTimer() *time.Timer {
timer := time.NewTimer(time.Second)
StopTimer(timer)
return timer
var tPool = sync.Pool{
New: func() any {
timer := time.NewTimer(time.Second)
StopTimer(timer)
return timer
},
}
// StopTimer stops t and drains the C channel.
// NewTimer returns an unusued timer from an internal timer pool.
// The timer is guaranteed to be stopped; meaning a call to timer.Reset() should be made before using it.
func NewTimer() *time.Timer {
return tPool.Get().(*time.Timer)
}
// StopTimer stops the given timer and drains the underlying channel.
// This prevents it from firing, until a call to Reset() is made.
//
// If the timer is not running, StopTimer does nothing.
func StopTimer(t *time.Timer) {
t.Stop()
@ -25,6 +35,12 @@ func StopTimer(t *time.Timer) {
}
}
// ReleaseTimer stops t and returns it to the pool of timers.
func ReleaseTimer(t *time.Timer) {
StopTimer(t)
tPool.Put(t)
}
// TickContext is like [time.Tick], but closes the returned channel once the context closes.
// As such it can be recovered by the garbage collector; see [time.TickContext].
//
@ -34,24 +50,26 @@ func TickContext(c context.Context, d time.Duration) <-chan time.Time {
return nil
}
timer := make(chan time.Time, 1)
timer <- time.Now()
ticker := make(chan time.Time, 1)
ticker <- time.Now()
go func() {
t := time.NewTicker(d)
defer t.Stop()
defer close(timer)
defer close(ticker)
timer := NewTimer()
defer ReleaseTimer(timer)
for {
timer.Reset(d)
select {
case tick := <-t.C:
timer <- tick
case tick := <-timer.C:
ticker <- tick
case <-c.Done():
return
}
}
}()
return timer
return ticker
}
// TickUntilFunc invokes f every d until either context is closed, or f returns true.