241 lines
5 KiB
Go
241 lines
5 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package op
|
|
|
|
import (
|
|
"fmt"
|
|
"sync/atomic"
|
|
)
|
|
|
|
type Signaler interface {
|
|
Completed()
|
|
Failed()
|
|
Canceled()
|
|
}
|
|
|
|
// splitSignal guards one output signaler from multiple calls
|
|
// by using a simple reference counting scheme. If one Signaler consumer
|
|
// reports a Failed event, the Failed event will be send to the guarded Signaler
|
|
// once the reference count becomes zero.
|
|
//
|
|
// Example use cases:
|
|
// - Push signaler to multiple outputers
|
|
// - split data to be send in smaller batches
|
|
type splitSignal struct {
|
|
count int32
|
|
signaler Signaler
|
|
|
|
// Flags to compute final state.
|
|
// Use atomic ops to determine final SignalResponse
|
|
canceled uint32
|
|
failed uint32
|
|
}
|
|
|
|
// compositeSignal combines multiple signalers into one Signaler forwarding an
|
|
// event to to all signalers.
|
|
type compositeSignal struct {
|
|
signalers []Signaler
|
|
}
|
|
|
|
type cancelableSignal struct {
|
|
canceler *Canceler
|
|
signaler Signaler
|
|
}
|
|
|
|
// SignalCallback converts a function accepting SignalResponse into
|
|
// a Signaler.
|
|
type SignalCallback func(SignalResponse)
|
|
|
|
type SignalChannel struct {
|
|
C chan SignalResponse
|
|
}
|
|
|
|
type SignalResponse uint8
|
|
|
|
const (
|
|
SignalCompleted SignalResponse = iota + 1
|
|
SignalFailed
|
|
SignalCanceled
|
|
)
|
|
|
|
func (f SignalCallback) Completed() {
|
|
f(SignalCompleted)
|
|
}
|
|
|
|
func (f SignalCallback) Failed() {
|
|
f(SignalFailed)
|
|
}
|
|
|
|
func (f SignalCallback) Canceled() {
|
|
f(SignalCanceled)
|
|
}
|
|
|
|
func (code SignalResponse) Apply(s Signaler) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
|
|
switch code {
|
|
case SignalCompleted:
|
|
s.Completed()
|
|
case SignalFailed:
|
|
s.Failed()
|
|
case SignalCanceled:
|
|
s.Canceled()
|
|
default:
|
|
panic(fmt.Errorf("Invalid signaler code: %v", code))
|
|
}
|
|
}
|
|
|
|
// NewSplitSignaler creates a new splitSignal if s is not nil.
|
|
// If s is nil, nil will be returned. The count is the number of events to be
|
|
// received before publishing the final event to the guarded Signaler.
|
|
func SplitSignaler(s Signaler, count int) Signaler {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
return &splitSignal{
|
|
count: int32(count),
|
|
signaler: s,
|
|
}
|
|
}
|
|
|
|
// Completed signals a Completed event to s.
|
|
func (s *splitSignal) Completed() {
|
|
s.onEvent()
|
|
}
|
|
|
|
// Failed signals a Failed event to s.
|
|
func (s *splitSignal) Failed() {
|
|
atomic.StoreUint32(&s.failed, 1)
|
|
s.onEvent()
|
|
}
|
|
|
|
func (s *splitSignal) Canceled() {
|
|
atomic.StoreUint32(&s.canceled, 1)
|
|
s.onEvent()
|
|
}
|
|
|
|
func (s *splitSignal) onEvent() {
|
|
res := atomic.AddInt32(&s.count, -1)
|
|
if res == 0 {
|
|
canceled := atomic.LoadUint32(&s.canceled)
|
|
failed := atomic.LoadUint32(&s.failed)
|
|
|
|
if canceled == 1 {
|
|
s.signaler.Canceled()
|
|
} else if failed == 1 {
|
|
s.signaler.Failed()
|
|
} else {
|
|
s.signaler.Completed()
|
|
}
|
|
}
|
|
}
|
|
|
|
// NewCompositeSignaler creates a new composite signaler.
|
|
func CombineSignalers(signalers ...Signaler) Signaler {
|
|
if len(signalers) == 0 {
|
|
return nil
|
|
}
|
|
return &compositeSignal{signalers}
|
|
}
|
|
|
|
// Completed sends the Completed signal to all signalers.
|
|
func (cs *compositeSignal) Completed() {
|
|
for _, s := range cs.signalers {
|
|
if s != nil {
|
|
s.Completed()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Failed sends the Failed signal to all signalers.
|
|
func (cs *compositeSignal) Failed() {
|
|
for _, s := range cs.signalers {
|
|
if s != nil {
|
|
s.Failed()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Canceled sends the Completed signal to all signalers.
|
|
func (cs *compositeSignal) Canceled() {
|
|
for _, s := range cs.signalers {
|
|
if s != nil {
|
|
s.Canceled()
|
|
}
|
|
}
|
|
}
|
|
|
|
func CancelableSignaler(c *Canceler, s Signaler) Signaler {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
return &cancelableSignal{canceler: c, signaler: s}
|
|
}
|
|
|
|
func (s *cancelableSignal) Completed() {
|
|
l := &s.canceler.lock
|
|
|
|
l.RLock()
|
|
if s.canceler.active {
|
|
defer l.RUnlock()
|
|
s.signaler.Completed()
|
|
} else {
|
|
l.RUnlock()
|
|
s.signaler.Canceled()
|
|
}
|
|
}
|
|
|
|
func (s *cancelableSignal) Failed() {
|
|
l := &s.canceler.lock
|
|
|
|
l.RLock()
|
|
if s.canceler.active {
|
|
defer l.RUnlock()
|
|
s.signaler.Failed()
|
|
} else {
|
|
l.RUnlock()
|
|
s.signaler.Canceled()
|
|
}
|
|
}
|
|
|
|
func (s *cancelableSignal) Canceled() {
|
|
s.signaler.Canceled()
|
|
}
|
|
|
|
func NewSignalChannel() *SignalChannel {
|
|
return &SignalChannel{make(chan SignalResponse, 1)}
|
|
}
|
|
|
|
func (s *SignalChannel) Completed() {
|
|
s.C <- SignalCompleted
|
|
}
|
|
|
|
func (s *SignalChannel) Failed() {
|
|
s.C <- SignalFailed
|
|
}
|
|
|
|
func (s *SignalChannel) Canceled() {
|
|
s.C <- SignalCanceled
|
|
}
|
|
|
|
func (s *SignalChannel) Wait() SignalResponse {
|
|
return <-s.C
|
|
}
|