1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

fix(offlinegate): fix data race in offlinegate EE-2713 (#6626)

This commit is contained in:
andres-portainer 2022-03-18 13:20:10 -03:00 committed by GitHub
parent a66e863646
commit 1ab65a4b4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 21 additions and 109 deletions

View file

@ -3,69 +3,48 @@ package offlinegate
import (
"log"
"net/http"
"sync"
"strings"
"time"
httperror "github.com/portainer/libhttp/error"
lock "github.com/viney-shih/go-lock"
)
// OfflineGate is a entity that works similar to a mutex with a signaling
// Only the caller that have Locked an gate can unlock it, otherw will be blocked with a call to Lock.
// OfflineGate is an entity that works similar to a mutex with signaling
// Only the caller that has Locked a gate can unlock it, otherwise it will be blocked with a call to Lock.
// Gate provides a passthrough http middleware that will wait for a locked gate to be unlocked.
// For a safety reasons, middleware will timeout
// For safety reasons, the middleware will timeout
type OfflineGate struct {
lock *sync.Mutex
signalingCh chan interface{}
lock *lock.CASMutex
}
// NewOfflineGate creates a new gate
func NewOfflineGate() *OfflineGate {
return &OfflineGate{
lock: &sync.Mutex{},
lock: lock.NewCASMutex(),
}
}
// Lock locks readonly gate and returns a function to unlock
func (o *OfflineGate) Lock() func() {
o.lock.Lock()
o.signalingCh = make(chan interface{})
return o.unlock
}
func (o *OfflineGate) unlock() {
if o.signalingCh == nil {
return
}
close(o.signalingCh)
o.signalingCh = nil
o.lock.Unlock()
}
// Watch returns a signaling channel.
// Unless channel is nil, client needs to watch for a signal on a channel to know when gate is unlocked.
// Signal channel is disposable: onced signaled, has to be disposed and acquired again.
func (o *OfflineGate) Watch() chan interface{} {
return o.signalingCh
return o.lock.Unlock
}
// WaitingMiddleware returns an http handler that waits for the gate to be unlocked before continuing
func (o *OfflineGate) WaitingMiddleware(timeout time.Duration, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
signalingCh := o.Watch()
if signalingCh != nil {
if r.Method != "GET" && r.Method != "HEAD" && r.Method != "OPTIONS" {
select {
case <-signalingCh:
case <-time.After(timeout):
log.Println("error: Timeout waiting for the offline gate to signal")
httperror.WriteError(w, http.StatusRequestTimeout, "Timeout waiting for the offline gate to signal", http.ErrHandlerTimeout)
}
}
if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" || strings.HasPrefix(r.URL.Path, "/api/backup") || strings.HasPrefix(r.URL.Path, "/api/restore") {
next.ServeHTTP(w, r)
return
}
if !o.lock.RTryLockWithTimeout(timeout) {
log.Println("error: Timeout waiting for the offline gate to signal")
httperror.WriteError(w, http.StatusRequestTimeout, "Timeout waiting for the offline gate to signal", http.ErrHandlerTimeout)
return
}
next.ServeHTTP(w, r)
o.lock.RUnlock()
})
}