feat: add blocking refresh frequency (#558)

* feat: add blocking refresh frequency

The refresh frequency when using the blocking strategy was set to 5 seconds. This is now configurable with a default value of 5 seconds.

* fail ci if codecov fail

* always upload codecov even if ci fails

* remove useless panic

* use fork

* add -short - revert later

* remove short

* publish test results
This commit is contained in:
Alexis Couvreur
2025-03-12 21:06:28 -04:00
committed by GitHub
parent fca9c79289
commit a897c65d39
21 changed files with 274 additions and 115 deletions

View File

@@ -17,6 +17,8 @@ jobs:
permissions:
contents: read
id-token: write # OIDC with Codecov
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
@@ -33,15 +35,26 @@ jobs:
run: go build -v ./cmd/sablier
- name: Test
run: go test -v -json -race -covermode atomic -coverprofile coverage.txt ./... 2>&1 | go tool go-junit-report -parser gojson > junit.xml
run: go test -v -json -race -covermode atomic -coverprofile coverage.txt ./... 2>&1 | go tool go-junit-report -parser gojson -set-exit-code > junit.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
if: ${{ !cancelled() }}
uses: acouvreur/codecov-action@main
with:
use_oidc: true
fail_ci_if_error: true
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
fail_ci_if_error: true
- name: Run Basic Test Results Action
if: ${{ !cancelled() }}
uses: codecov/basic-test-results@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
file: junit.xml
disable-search: true

View File

@@ -27,7 +27,7 @@ build:
go build -v ./cmd/sablier
test:
go test -v ./...
go test ./...
plugins: build-plugin-traefik test-plugin-traefik build-plugin-caddy test-plugin-caddy

View File

@@ -1,6 +1,7 @@
package main
import (
"errors"
"fmt"
"github.com/sablierapp/sablier/cmd/healthcheck"
"github.com/sablierapp/sablier/cmd/version"
@@ -43,7 +44,7 @@ It provides an integrations with multiple reverse proxies and different loading
rootCmd.PersistentFlags().StringVar(&cfgFile, "configFile", "", "Config file path. If not defined, looks for sablier.(yml|yaml|toml) in /etc/sablier/ > $XDG_CONFIG_HOME > $HOME/.config/ and current directory")
startCmd := NewCmd()
startCmd := newStartCommand()
// Provider flags
startCmd.Flags().StringVar(&conf.Provider.Name, "provider.name", "docker", fmt.Sprintf("Provider to use to manage containers %v", config.GetProviders()))
viper.BindPFlag("provider.name", startCmd.Flags().Lookup("provider.name"))
@@ -84,6 +85,8 @@ It provides an integrations with multiple reverse proxies and different loading
viper.BindPFlag("strategy.dynamic.default-refresh-frequency", startCmd.Flags().Lookup("strategy.dynamic.default-refresh-frequency"))
startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultTimeout, "strategy.blocking.default-timeout", 1*time.Minute, "Default timeout used for blocking strategy")
viper.BindPFlag("strategy.blocking.default-timeout", startCmd.Flags().Lookup("strategy.blocking.default-timeout"))
startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultRefreshFrequency, "strategy.blocking.default-refresh-frequency", 5*time.Second, "Default refresh frequency at which the instances status are checked for blocking strategy")
viper.BindPFlag("strategy.blocking.default-refresh-frequency", startCmd.Flags().Lookup("strategy.blocking.default-refresh-frequency"))
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(version.NewCmd())
@@ -115,10 +118,8 @@ func initializeConfig(cmd *cobra.Command) error {
// if we cannot parse the config file.
if err := v.ReadInConfig(); err != nil {
// It's okay if there isn't a config file
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return err
} else if cfgFile != "" {
// But if we explicitely defined the config file it should return the error
var configFileNotFoundError viper.ConfigFileNotFoundError
if !errors.As(err, &configFileNotFoundError) {
return err
}
}
@@ -149,7 +150,7 @@ func bindFlags(cmd *cobra.Command, v *viper.Viper) {
})
}
func NewCmd() *cobra.Command {
var newStartCommand = func() *cobra.Command {
return &cobra.Command{
Use: "start",
Short: "Start the Sablier server",

186
cmd/sablier/cmd_test.go Normal file
View File

@@ -0,0 +1,186 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"github.com/sablierapp/sablier/pkg/config"
"os"
"path/filepath"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"gotest.tools/v3/assert"
)
func TestDefault(t *testing.T) {
testDir, err := os.Getwd()
require.NoError(t, err, "error getting the current working directory")
wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_default.json"))
require.NoError(t, err, "error reading test config file")
// CHANGE `startCmd` behavior to only print the config, this is for testing purposes only
newStartCommand = mockStartCommand
t.Run("config file", func(t *testing.T) {
conf = config.NewConfig()
cmd := NewRootCommand()
output := &bytes.Buffer{}
cmd.SetOut(output)
cmd.SetArgs([]string{
"start",
})
cmd.Execute()
gotOutput := output.String()
assert.Equal(t, string(wantConfig), gotOutput)
})
}
func TestPrecedence(t *testing.T) {
testDir, err := os.Getwd()
require.NoError(t, err, "error getting the current working directory")
// CHANGE `startCmd` behavior to only print the config, this is for testing purposes only
newStartCommand = mockStartCommand
t.Run("config file", func(t *testing.T) {
wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_yaml_wanted.json"))
require.NoError(t, err, "error reading test config file")
conf = config.NewConfig()
cmd := NewRootCommand()
output := &bytes.Buffer{}
cmd.SetOut(output)
cmd.SetArgs([]string{
"--configFile", filepath.Join(testDir, "testdata", "config.yml"),
"start",
})
cmd.Execute()
gotOutput := output.String()
assert.Equal(t, string(wantConfig), gotOutput)
})
t.Run("env var", func(t *testing.T) {
setEnvsFromFile(filepath.Join(testDir, "testdata", "config.env"))
defer unsetEnvsFromFile(filepath.Join(testDir, "testdata", "config.env"))
wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_env_wanted.json"))
require.NoError(t, err, "error reading test config file")
conf = config.NewConfig()
cmd := NewRootCommand()
output := &bytes.Buffer{}
cmd.SetOut(output)
cmd.SetArgs([]string{
"--configFile", filepath.Join(testDir, "testdata", "config.yml"),
"start",
})
cmd.Execute()
gotOutput := output.String()
assert.Equal(t, string(wantConfig), gotOutput)
})
t.Run("flag", func(t *testing.T) {
setEnvsFromFile(filepath.Join(testDir, "testdata", "config.env"))
defer unsetEnvsFromFile(filepath.Join(testDir, "testdata", "config.env"))
wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_cli_wanted.json"))
require.NoError(t, err, "error reading test config file")
cmd := NewRootCommand()
output := &bytes.Buffer{}
conf = config.NewConfig()
cmd.SetOut(output)
cmd.SetArgs([]string{
"--configFile", filepath.Join(testDir, "testdata", "config.yml"),
"start",
"--provider.name", "cli",
"--provider.kubernetes.qps", "256",
"--provider.kubernetes.burst", "512",
"--provider.kubernetes.delimiter", "_",
"--server.port", "3333",
"--server.base-path", "/cli/",
"--storage.file", "/tmp/cli.json",
"--sessions.default-duration", "3h",
"--sessions.expiration-interval", "3h",
"--logging.level", "info",
"--strategy.dynamic.custom-themes-path", "/tmp/cli/themes",
// Must use `=` see https://github.com/spf13/cobra/issues/613
"--strategy.dynamic.show-details-by-default=false",
"--strategy.dynamic.default-theme", "cli",
"--strategy.dynamic.default-refresh-frequency", "3h",
"--strategy.blocking.default-timeout", "3h",
"--strategy.blocking.default-refresh-frequency", "3h",
})
cmd.Execute()
gotOutput := output.String()
assert.Equal(t, string(wantConfig), gotOutput)
})
}
func setEnvsFromFile(path string) {
readFile, err := os.Open(path)
if err != nil {
panic(err)
}
defer readFile.Close()
fileScanner := bufio.NewScanner(readFile)
fileScanner.Split(bufio.ScanLines)
for fileScanner.Scan() {
split := strings.Split(fileScanner.Text(), "=")
os.Setenv(split[0], split[1])
}
}
func unsetEnvsFromFile(path string) {
readFile, err := os.Open(path)
if err != nil {
panic(err)
}
defer readFile.Close()
fileScanner := bufio.NewScanner(readFile)
fileScanner.Split(bufio.ScanLines)
for fileScanner.Scan() {
split := strings.Split(fileScanner.Text(), "=")
os.Unsetenv(split[0])
}
}
func mockStartCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "InstanceStart the Sablier server",
Run: func(cmd *cobra.Command, args []string) {
viper.Unmarshal(&conf)
out := cmd.OutOrStdout()
encoder := json.NewEncoder(out)
encoder.SetIndent("", " ")
encoder.Encode(conf)
},
}
return cmd
}

View File

@@ -36,6 +36,7 @@ func Start(ctx context.Context, conf config.Config) error {
}
s := sablier.New(logger, store, provider)
s.BlockingRefreshFrequency = conf.Strategy.Blocking.DefaultRefreshFrequency
groups, err := provider.InstanceGroups(ctx)
if err != nil {

View File

@@ -13,4 +13,5 @@ STRATEGY_DYNAMIC_CUSTOM_THEMES_PATH=/tmp/envvar/themes
STRATEGY_SHOW_DETAILS_BY_DEFAULT=false
STRATEGY_DYNAMIC_DEFAULT_THEME=envvar
STRATEGY_DYNAMIC_DEFAULT_REFRESH_FREQUENCY=2h
STRATEGY_BLOCKING_DEFAULT_TIMEOUT=2h
STRATEGY_BLOCKING_DEFAULT_TIMEOUT=2h
STRATEGY_BLOCKING_DEFAULT_REFRESH_FREQUENCY=2h

View File

@@ -22,4 +22,5 @@ strategy:
default-theme: configfile
default-refresh-frequency: 1h
blocking:
default-timeout: 1h
default-timeout: 1h
default-refresh-frequency: 1h

View File

@@ -30,7 +30,8 @@
"DefaultRefreshFrequency": 10800000000000
},
"Blocking": {
"DefaultTimeout": 10800000000000
"DefaultTimeout": 10800000000000,
"DefaultRefreshFrequency": 10800000000000
}
}
}

View File

@@ -30,7 +30,8 @@
"DefaultRefreshFrequency": 5000000000
},
"Blocking": {
"DefaultTimeout": 60000000000
"DefaultTimeout": 60000000000,
"DefaultRefreshFrequency": 5000000000
}
}
}

View File

@@ -30,7 +30,8 @@
"DefaultRefreshFrequency": 7200000000000
},
"Blocking": {
"DefaultTimeout": 7200000000000
"DefaultTimeout": 7200000000000,
"DefaultRefreshFrequency": 7200000000000
}
}
}

View File

@@ -30,7 +30,8 @@
"DefaultRefreshFrequency": 3600000000000
},
"Blocking": {
"DefaultTimeout": 3600000000000
"DefaultTimeout": 3600000000000,
"DefaultRefreshFrequency": 3600000000000
}
}
}

View File

@@ -1,15 +1,26 @@
package api
import (
config2 "github.com/sablierapp/sablier/pkg/config"
"context"
"github.com/sablierapp/sablier/pkg/config"
"github.com/sablierapp/sablier/pkg/sablier"
"github.com/sablierapp/sablier/pkg/theme"
"time"
)
//go:generate go tool mockgen -package apitest -source=api.go -destination=apitest/mocks_sablier.go *
type Sablier interface {
RequestSession(ctx context.Context, names []string, duration time.Duration) (*sablier.SessionState, error)
RequestSessionGroup(ctx context.Context, group string, duration time.Duration) (*sablier.SessionState, error)
RequestReadySession(ctx context.Context, names []string, duration time.Duration, timeout time.Duration) (*sablier.SessionState, error)
RequestReadySessionGroup(ctx context.Context, group string, duration time.Duration, timeout time.Duration) (*sablier.SessionState, error)
}
type ServeStrategy struct {
Theme *theme.Themes
Sablier sablier.Sablier
StrategyConfig config2.Strategy
SessionsConfig config2.Sessions
Sablier Sablier
StrategyConfig config.Strategy
SessionsConfig config.Sessions
}

View File

@@ -3,8 +3,8 @@ package api
import (
"github.com/gin-gonic/gin"
"github.com/neilotoole/slogt"
"github.com/sablierapp/sablier/internal/api/apitest"
config2 "github.com/sablierapp/sablier/pkg/config"
"github.com/sablierapp/sablier/pkg/sablier/sabliertest"
"github.com/sablierapp/sablier/pkg/theme"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
@@ -13,7 +13,7 @@ import (
"testing"
)
func NewApiTest(t *testing.T) (app *gin.Engine, router *gin.RouterGroup, strategy *ServeStrategy, mock *sabliertest.MockSablier) {
func NewApiTest(t *testing.T) (app *gin.Engine, router *gin.RouterGroup, strategy *ServeStrategy, mock *apitest.MockSablier) {
t.Helper()
gin.SetMode(gin.TestMode)
ctrl := gomock.NewController(t)
@@ -22,7 +22,7 @@ func NewApiTest(t *testing.T) (app *gin.Engine, router *gin.RouterGroup, strateg
app = gin.New()
router = app.Group("/api")
mock = sabliertest.NewMockSablier(ctrl)
mock = apitest.NewMockSablier(ctrl)
strategy = &ServeStrategy{
Theme: th,
Sablier: mock,

View File

@@ -1,13 +1,13 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: sablier.go
// Source: api.go
//
// Generated by this command:
//
// mockgen -package sabliertest -source=sablier.go -destination=sabliertest/mocks_sablier.go *
// mockgen -package apitest -source=api.go -destination=apitest/mocks_sablier.go *
//
// Package sabliertest is a generated GoMock package.
package sabliertest
// Package apitest is a generated GoMock package.
package apitest
import (
context "context"
@@ -42,32 +42,6 @@ func (m *MockSablier) EXPECT() *MockSablierMockRecorder {
return m.recorder
}
// GroupWatch mocks base method.
func (m *MockSablier) GroupWatch(ctx context.Context) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "GroupWatch", ctx)
}
// GroupWatch indicates an expected call of GroupWatch.
func (mr *MockSablierMockRecorder) GroupWatch(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupWatch", reflect.TypeOf((*MockSablier)(nil).GroupWatch), ctx)
}
// RemoveInstance mocks base method.
func (m *MockSablier) RemoveInstance(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RemoveInstance", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// RemoveInstance indicates an expected call of RemoveInstance.
func (mr *MockSablierMockRecorder) RemoveInstance(ctx, name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveInstance", reflect.TypeOf((*MockSablier)(nil).RemoveInstance), ctx, name)
}
// RequestReadySession mocks base method.
func (m *MockSablier) RequestReadySession(ctx context.Context, names []string, duration, timeout time.Duration) (*sablier.SessionState, error) {
m.ctrl.T.Helper()
@@ -127,29 +101,3 @@ func (mr *MockSablierMockRecorder) RequestSessionGroup(ctx, group, duration any)
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestSessionGroup", reflect.TypeOf((*MockSablier)(nil).RequestSessionGroup), ctx, group, duration)
}
// SetGroups mocks base method.
func (m *MockSablier) SetGroups(groups map[string][]string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetGroups", groups)
}
// SetGroups indicates an expected call of SetGroups.
func (mr *MockSablierMockRecorder) SetGroups(groups any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetGroups", reflect.TypeOf((*MockSablier)(nil).SetGroups), groups)
}
// StopAllUnregisteredInstances mocks base method.
func (m *MockSablier) StopAllUnregisteredInstances(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StopAllUnregisteredInstances", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// StopAllUnregisteredInstances indicates an expected call of StopAllUnregisteredInstances.
func (mr *MockSablierMockRecorder) StopAllUnregisteredInstances(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopAllUnregisteredInstances", reflect.TypeOf((*MockSablier)(nil).StopAllUnregisteredInstances), ctx)
}

View File

@@ -10,7 +10,8 @@ type DynamicStrategy struct {
}
type BlockingStrategy struct {
DefaultTimeout time.Duration `mapstructure:"DEFAULT_TIMEOUT" yaml:"defaultTimeout" default:"1m"`
DefaultTimeout time.Duration `mapstructure:"DEFAULT_TIMEOUT" yaml:"defaultTimeout" default:"1m"`
DefaultRefreshFrequency time.Duration `mapstructure:"DEFAULT_REFRESH_FREQUENCY" yaml:"defaultRefreshFrequency" default:"5s"`
}
type Strategy struct {
@@ -35,6 +36,7 @@ func newDynamicStrategy() DynamicStrategy {
func newBlockingStrategy() BlockingStrategy {
return BlockingStrategy{
DefaultTimeout: 1 * time.Minute,
DefaultTimeout: 1 * time.Minute,
DefaultRefreshFrequency: 5 * time.Second,
}
}

View File

@@ -13,7 +13,7 @@ import (
// as running instances by Sablier.
// By default, Sablier does not stop all already running instances. Meaning that you need to make an
// initial request in order to trigger the scaling to zero.
func (s *sablier) StopAllUnregisteredInstances(ctx context.Context) error {
func (s *Sablier) StopAllUnregisteredInstances(ctx context.Context) error {
instances, err := s.provider.InstanceList(ctx, provider.InstanceListOptions{
All: false, // Only running instances
})
@@ -40,7 +40,7 @@ func (s *sablier) StopAllUnregisteredInstances(ctx context.Context) error {
return waitGroup.Wait()
}
func (s *sablier) stopFunc(ctx context.Context, name string) func() error {
func (s *Sablier) stopFunc(ctx context.Context, name string) func() error {
return func() error {
err := s.provider.InstanceStop(ctx, name)
if err != nil {

View File

@@ -6,7 +6,7 @@ import (
"time"
)
func (s *sablier) GroupWatch(ctx context.Context) {
func (s *Sablier) GroupWatch(ctx context.Context) {
// This should be changed to event based instead of polling.
ticker := time.NewTicker(2 * time.Second)
for {

View File

@@ -9,7 +9,7 @@ import (
"time"
)
func (s *sablier) InstanceRequest(ctx context.Context, name string, duration time.Duration) (InstanceInfo, error) {
func (s *Sablier) InstanceRequest(ctx context.Context, name string, duration time.Duration) (InstanceInfo, error) {
if name == "" {
return InstanceInfo{}, errors.New("instance name cannot be empty")
}

View File

@@ -8,41 +8,32 @@ import (
"time"
)
//go:generate go tool mockgen -package sabliertest -source=sablier.go -destination=sabliertest/mocks_sablier.go *
type Sablier interface {
RequestSession(ctx context.Context, names []string, duration time.Duration) (*SessionState, error)
RequestSessionGroup(ctx context.Context, group string, duration time.Duration) (*SessionState, error)
RequestReadySession(ctx context.Context, names []string, duration time.Duration, timeout time.Duration) (*SessionState, error)
RequestReadySessionGroup(ctx context.Context, group string, duration time.Duration, timeout time.Duration) (*SessionState, error)
RemoveInstance(ctx context.Context, name string) error
SetGroups(groups map[string][]string)
StopAllUnregisteredInstances(ctx context.Context) error
GroupWatch(ctx context.Context)
}
type sablier struct {
type Sablier struct {
provider Provider
sessions Store
groupsMu sync.RWMutex
groups map[string][]string
// BlockingRefreshFrequency is the frequency at which the instances are checked
// against the provider. Defaults to 5 seconds.
BlockingRefreshFrequency time.Duration
l *slog.Logger
}
func New(logger *slog.Logger, store Store, provider Provider) Sablier {
return &sablier{
provider: provider,
sessions: store,
groupsMu: sync.RWMutex{},
groups: map[string][]string{},
l: logger,
func New(logger *slog.Logger, store Store, provider Provider) *Sablier {
return &Sablier{
provider: provider,
sessions: store,
groupsMu: sync.RWMutex{},
groups: map[string][]string{},
l: logger,
BlockingRefreshFrequency: 5 * time.Second,
}
}
func (s *sablier) SetGroups(groups map[string][]string) {
func (s *Sablier) SetGroups(groups map[string][]string) {
s.groupsMu.Lock()
defer s.groupsMu.Unlock()
if groups == nil {
@@ -55,6 +46,6 @@ func (s *sablier) SetGroups(groups map[string][]string) {
}
}
func (s *sablier) RemoveInstance(ctx context.Context, name string) error {
func (s *Sablier) RemoveInstance(ctx context.Context, name string) error {
return s.sessions.Delete(ctx, name)
}

View File

@@ -9,7 +9,7 @@ import (
"testing"
)
func setupSablier(t *testing.T) (sablier.Sablier, *storetest.MockStore, *providertest.MockProvider) {
func setupSablier(t *testing.T) (*sablier.Sablier, *storetest.MockStore, *providertest.MockProvider) {
t.Helper()
ctrl := gomock.NewController(t)

View File

@@ -15,7 +15,7 @@ type InstanceInfoWithError struct {
Error error `json:"error"`
}
func (s *sablier) RequestSession(ctx context.Context, names []string, duration time.Duration) (sessionState *SessionState, err error) {
func (s *Sablier) RequestSession(ctx context.Context, names []string, duration time.Duration) (sessionState *SessionState, err error) {
if len(names) == 0 {
return nil, fmt.Errorf("names cannot be empty")
}
@@ -47,7 +47,7 @@ func (s *sablier) RequestSession(ctx context.Context, names []string, duration t
return sessionState, nil
}
func (s *sablier) RequestSessionGroup(ctx context.Context, group string, duration time.Duration) (sessionState *SessionState, err error) {
func (s *Sablier) RequestSessionGroup(ctx context.Context, group string, duration time.Duration) (sessionState *SessionState, err error) {
if len(group) == 0 {
return nil, fmt.Errorf("group is mandatory")
}
@@ -67,7 +67,7 @@ func (s *sablier) RequestSessionGroup(ctx context.Context, group string, duratio
return s.RequestSession(ctx, names, duration)
}
func (s *sablier) RequestReadySession(ctx context.Context, names []string, duration time.Duration, timeout time.Duration) (*SessionState, error) {
func (s *Sablier) RequestReadySession(ctx context.Context, names []string, duration time.Duration, timeout time.Duration) (*SessionState, error) {
session, err := s.RequestSession(ctx, names, duration)
if err != nil {
return nil, err
@@ -77,7 +77,7 @@ func (s *sablier) RequestReadySession(ctx context.Context, names []string, durat
return session, nil
}
ticker := time.NewTicker(5 * time.Second)
ticker := time.NewTicker(s.BlockingRefreshFrequency)
readiness := make(chan *SessionState)
errch := make(chan error)
quit := make(chan struct{})
@@ -121,7 +121,7 @@ func (s *sablier) RequestReadySession(ctx context.Context, names []string, durat
}
}
func (s *sablier) RequestReadySessionGroup(ctx context.Context, group string, duration time.Duration, timeout time.Duration) (sessionState *SessionState, err error) {
func (s *Sablier) RequestReadySessionGroup(ctx context.Context, group string, duration time.Duration, timeout time.Duration) (sessionState *SessionState, err error) {
if len(group) == 0 {
return nil, fmt.Errorf("group is mandatory")