<commit_before>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed 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 etcd provides an implementation of master election based on etcd.
// TODO(pavelkalinnikov): Add tests.
package etcd

import (
	"context"
	"fmt"
	"strings"

	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/clientv3/concurrency"
	"github.com/golang/glog"
	"github.com/google/trillian/util/election2"
)

// Election is an implementation of election2.Election based on etcd.
type Election struct {
	resourceID string
	instanceID string
	lockFile   string

	client   *clientv3.Client
	session  *concurrency.Session
	election *concurrency.Election
}

// Await blocks until the instance captures mastership.
func (e *Election) Await(ctx context.Context) error {
	return e.election.Campaign(ctx, e.instanceID)
}

// WithMastership returns a "mastership context" which remains active until the
// instance stops being the master, or the passed in context is canceled.
func (e *Election) WithMastership(ctx context.Context) (context.Context, error) {
	// Get a channel for notifications of election status (using the cancelable
	// context so that the monitoring goroutine below and the goroutine started
	// by WithMastership will reliably terminate).
	cctx, cancel := context.WithCancel(ctx)
	etcdRev := e.election.Rev() // The revision at which e became the master.
	if etcdRev == 0 {
		// Not even tried to become the master. Return a canceled context.
		cancel()
		return cctx, nil
	}

	// Was the master once, so watch for latest mastership updates.
	ch := e.election.Observe(cctx)
	// Verify that we are still the master, before returning context.
	select {
	case <-ctx.Done():
		cancel()
		return nil, ctx.Err()
	case rsp, ok := <-ch:
		if !ok || rsp.Kvs[0].CreateRevision != etcdRev {
			// Mastership has been overtaken in the meantime, or not capturead at all.
			cancel()
			return cctx, nil
		}
	}

	// At this point we have observed confirmation that we are the master; start
	// a goroutine to monitor for anyone else overtaking us.
	go func() {
		defer func() {
			cancel()
			glog.Infof("%s: canceled mastership context", e.resourceID)
		}()

		for rsp := range ch {
			if rsp.Kvs[0].CreateRevision != etcdRev {
				conquerorID := string(rsp.Kvs[0].Value)
				glog.Warningf("%s: mastership overtaken by %s", e.resourceID, conquerorID)
				break
			}
		}
	}()

	return cctx, nil
}

// Resign releases mastership for this instance. The instance can be elected
// again using Await. Idempotent, might be useful to retry if fails.
func (e *Election) Resign(ctx context.Context) error {
	return e.election.Resign(ctx)
}

// Close resigns and permanently stops participating in election. No other
// method should be called after Close.
func (e *Election) Close(ctx context.Context) error {
	if err := e.Resign(ctx); err != nil && err != concurrency.ErrElectionNotLeader {
		glog.Errorf("%s: Resign(): %v", e.resourceID, err)
	}
	// Session's Close revokes the underlying lease, which results in removing
	// the election-related keys. This achieves the effect of resignation even if
	// the above Resign call failed (e.g. due to ctx cancelation).
	return e.session.Close()
}

// Factory creates Election instances.
type Factory struct {
	client     *clientv3.Client
	instanceID string<commit_msg>election2/etcd: Proclaim resignation (#1388)<commit_after>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed 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 etcd provides an implementation of master election based on etcd.
// TODO(pavelkalinnikov): Add tests.
package etcd

import (
	"context"
	"fmt"
	"strings"

	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/clientv3/concurrency"
	"github.com/golang/glog"
	"github.com/google/trillian/util/election2"
)

const resignID = "<resign>"

// Election is an implementation of election2.Election based on etcd.
type Election struct {
	resourceID string
	instanceID string
	lockFile   string

	client   *clientv3.Client
	session  *concurrency.Session
	election *concurrency.Election
}

// Await blocks until the instance captures mastership.
func (e *Election) Await(ctx context.Context) error {
	return e.election.Campaign(ctx, e.instanceID)
}

// WithMastership returns a "mastership context" which remains active until the
// instance stops being the master, or the passed in context is canceled.
func (e *Election) WithMastership(ctx context.Context) (context.Context, error) {
	// Get a channel for notifications of election status (using the cancelable
	// context so that the monitoring goroutine below and the goroutine started
	// by WithMastership will reliably terminate).
	cctx, cancel := context.WithCancel(ctx)
	etcdRev := e.election.Rev() // The revision at which e became the master.
	if etcdRev == 0 {
		// Not even tried to become the master. Return a canceled context.
		cancel()
		return cctx, nil
	}

	// Was the master once, so watch for latest mastership updates.
	ch := e.election.Observe(cctx)
	// Verify that we are still the master, before returning context.
	select {
	case <-ctx.Done():
		cancel()
		return nil, ctx.Err()
	case rsp, ok := <-ch:
		kv := rsp.Kvs[0]
		if !ok || kv.CreateRevision != etcdRev || string(kv.Value) == resignID {
			// Mastership has been overtaken, released, or not capturead at all.
			cancel()
			return cctx, nil
		}
	}

	// At this point we have observed confirmation that we are the master; start
	// a goroutine to monitor for anyone else overtaking us.
	go func() {
		defer func() {
			cancel()
			glog.Infof("%s: canceled mastership context", e.resourceID)
		}()

		for rsp := range ch {
			kv := rsp.Kvs[0]
			if kv.CreateRevision != etcdRev {
				conquerorID := string(kv.Value)
				// TODO(pavelkalinnikov): conquerorID can be resignID too. Serialize a
				// protobuf with all mastership details instead of ID string.
				glog.Warningf("%s: mastership overtaken by %s", e.resourceID, conquerorID)
				break
			} else if string(kv.Value) == resignID {
				glog.Infof("%s: canceling context due to resignation", e.resourceID)
				break
			}
		}
	}()

	return cctx, nil
}

// Resign releases mastership for this instance. The instance can be elected
// again using Await. Idempotent, might be useful to retry if fails.
func (e *Election) Resign(ctx context.Context) error {
	// Trigger Observe callers to see the update, and cancel mastership contexts.
	err := e.election.Proclaim(ctx, resignID)
	if err == concurrency.ErrElectionNotLeader {
		return nil // Resigning if not master is a no-op.
	} else if err != nil {
		return err
	}
	return e.election.Resign(ctx)
}

// Close resigns and permanently stops participating in election. No other
// method should be called after Close.
func (e *Election) Close(ctx context.Context) error {
	if err := e.Resign(ctx); err != nil && err != concurrency.ErrElectionNotLeader {
		glog.Errorf("%s: Resign(): %v", e.resourceID, err)
	}
	// Session's Close revokes the underlying lease, which results in removing
	// the election-related keys. This achieves the effect of resignation even if
	// the above Resign call failed (e.g. due to ctx cancelation).
	return e.session.Close()
}

// Factory creates Election instances.
type Factory struct {
	client     *clientv3.Client
	instanceID string
{'fraction_non_alphanumeric': 0.10001202067556196, 'fraction_numerical': 0.004808270224786633, 'mean_word_length': 3.6454494695700728, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7198238', 'n_tokens_mistral': 2630, 'n_tokens_neox': 2457, 'n_words': 970}