| Index: filter/dscache/globalconfig.go
 | 
| diff --git a/filter/dscache/globalconfig.go b/filter/dscache/globalconfig.go
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..74212e50340207449f3b36b9dad4b47f0bc305d0
 | 
| --- /dev/null
 | 
| +++ b/filter/dscache/globalconfig.go
 | 
| @@ -0,0 +1,107 @@
 | 
| +// Copyright 2015 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +package dscache
 | 
| +
 | 
| +import (
 | 
| +	"sync"
 | 
| +	"time"
 | 
| +
 | 
| +	"github.com/luci/gae/service/datastore"
 | 
| +	"github.com/luci/gae/service/info"
 | 
| +	"github.com/luci/gae/service/memcache"
 | 
| +	"github.com/luci/luci-go/common/clock"
 | 
| +	"golang.org/x/net/context"
 | 
| +)
 | 
| +
 | 
| +type GlobalConfig struct {
 | 
| +	_id   int64  `gae:"$id,1"`
 | 
| +	_kind string `gae:"$kind,dscache"`
 | 
| +
 | 
| +	Enable bool
 | 
| +}
 | 
| +
 | 
| +var (
 | 
| +	globalEnabledLock = sync.RWMutex{}
 | 
| +
 | 
| +	// globalEnabled is whether or not memcache has been globally enabled. It is
 | 
| +	// populated by IsGloballyEnabled when SetGlobalEnable has been set to
 | 
| +	// true.
 | 
| +	globalEnabled = true
 | 
| +
 | 
| +	// globalEnabledNextCheck is IsGloballyEnabled's last successful check of the
 | 
| +	// global disable key.
 | 
| +	globalEnabledNextCheck = time.Time{}
 | 
| +)
 | 
| +
 | 
| +// IsGloballyEnabled checks to see if this filter is enabled globally.
 | 
| +//
 | 
| +// This checks InstanceEnabledStatic, as well as polls the datastore entity
 | 
| +//   /dscache,1 (a GlobalConfig instance)
 | 
| +// Once every GlobalEnabledCheckInterval.
 | 
| +//
 | 
| +// For correctness, any error encountered returns true. If this assumed false,
 | 
| +// then Put operations might incorrectly invalidate the cache.
 | 
| +func IsGloballyEnabled(c context.Context) bool {
 | 
| +	if !InstanceEnabledStatic {
 | 
| +		return false
 | 
| +	}
 | 
| +
 | 
| +	now := clock.Now(c)
 | 
| +
 | 
| +	globalEnabledLock.RLock()
 | 
| +	nextCheck := globalEnabledNextCheck
 | 
| +	enabledVal := globalEnabled
 | 
| +	globalEnabledLock.RUnlock()
 | 
| +
 | 
| +	if now.Before(nextCheck) {
 | 
| +		return enabledVal
 | 
| +	}
 | 
| +
 | 
| +	globalEnabledLock.Lock()
 | 
| +	defer globalEnabledLock.Unlock()
 | 
| +	// just in case we raced
 | 
| +	if now.Before(globalEnabledNextCheck) {
 | 
| +		return globalEnabled
 | 
| +	}
 | 
| +
 | 
| +	// always go to the default namespace
 | 
| +	c, err := info.Get(c).Namespace("")
 | 
| +	if err != nil {
 | 
| +		return true
 | 
| +	}
 | 
| +	cfg := &GlobalConfig{Enable: true}
 | 
| +	if err := datastore.Get(c).Get(cfg); err != nil && err != datastore.ErrNoSuchEntity {
 | 
| +		return true
 | 
| +	}
 | 
| +	globalEnabled = cfg.Enable
 | 
| +	globalEnabledNextCheck = now.Add(GlobalEnabledCheckInterval)
 | 
| +	return globalEnabled
 | 
| +}
 | 
| +
 | 
| +func SetGlobalEnable(c context.Context, memcacheEnabled bool) error {
 | 
| +	// always go to the default namespace
 | 
| +	c, err := info.Get(c).Namespace("")
 | 
| +	if err != nil {
 | 
| +		return err
 | 
| +	}
 | 
| +	return datastore.Get(c).RunInTransaction(func(c context.Context) error {
 | 
| +		ds := datastore.Get(c)
 | 
| +		cfg := &GlobalConfig{Enable: true}
 | 
| +		if err := ds.Get(cfg); err != nil && err != datastore.ErrNoSuchEntity {
 | 
| +			return err
 | 
| +		}
 | 
| +		if cfg.Enable == memcacheEnabled {
 | 
| +			return nil
 | 
| +		}
 | 
| +		cfg.Enable = memcacheEnabled
 | 
| +		if memcacheEnabled {
 | 
| +			// when going false -> true, wipe memcache.
 | 
| +			if err := memcache.Get(c).Flush(); err != nil {
 | 
| +				return err
 | 
| +			}
 | 
| +		}
 | 
| +		return ds.Put(cfg)
 | 
| +	}, nil)
 | 
| +}
 | 
| 
 |