<commit_before>package state

import (
	"errors"
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"labix.org/v2/mgo/txn"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/trivial"
	"sort"
	"strconv"
)

// Service represents the state of a service.
type Service struct {
	st  *State
	doc serviceDoc
	annotator
}

// serviceDoc represents the internal state of a service in MongoDB.
type serviceDoc struct {
	Name          string `bson:"_id"`
	CharmURL      *charm.URL
	ForceCharm    bool
	Life          Life
	UnitSeq       int
	UnitCount     int
	RelationCount int
	Exposed       bool
	TxnRevno      int64 `bson:"txn-revno"`
	Annotations   map[string]string
}

func newService(st *State, doc *serviceDoc) *Service {
	svc := &Service{
		st:  st,
		doc: *doc,
		annotator: annotator{
			st:   st,
			coll: st.services.Name,
			id:   doc.Name,
		},
	}
	svc.annotator.annotations = &svc.doc.Annotations
	return svc
}

// Name returns the service name.
func (s *Service) Name() string {
	return s.doc.Name
}

// EntityName returns a name identifying the service that is safe to use
// as a file name.  The returned name will be different from other
// EntityName values returned by any other entities from the same state.
func (s *Service) EntityName() string {
	return "service-" + s.Name()
}

// PasswordValid currently just returns false. Implemented here so that
// a service can be used as an Entity.
func (s *Service) PasswordValid(password string) bool {
	return false
}

// SetPassword currently just returns an error. Implemented here so that
// a service can be used as an Entity.
func (s *Service) SetPassword(password string) error {
	return fmt.Errorf("cannot set password of service")
}

// Annotations returns the service annotations.
func (s *Service) Annotations() map[string]string {
	return s.doc.Annotations
}

// globalKey returns the global database key for the service.
func (s *Service) globalKey() string {
	return "s#" + s.doc.Name
}

// Life returns whether the service is Alive, Dying or Dead.
func (s *Service) Life() Life {
	return s.doc.Life
}

var errRefresh = errors.New("cannot determine relation destruction operations; please refresh the service")

// Destroy ensures that the service and all its relations will be removed at
// some point; if the service has no units, and no relation involving the
// service has any units in scope, they are all removed immediately.
func (s *Service) Destroy() (err error) {
	defer trivial.ErrorContextf(&err, "cannot destroy service %q", s)
	defer func() {
		if err == nil {
			// This is a white lie; the document might actually be removed.
			s.doc.Life = Dying
		}
	}()
	svc := &Service{st: s.st, doc: s.doc}
	for i := 0; i < 5; i++ {
		ops, err := svc.destroyOps()
		switch {
		case err == errRefresh:
		case err == errAlreadyDying:
			return nil
		case err != nil:
			return err
		default:
			if err := svc.st.runner.Run(ops, "", nil); err != txn.ErrAborted {
				return err
			}
		}
		if err := svc.Refresh(); IsNotFound(err) {
			return nil
		} else if err != nil {
			return err
		}
	}
	return ErrExcessiveContention
}

// destroyOps returns the operations required to destroy the service. If it
// returns errRefresh, the service should be refreshed and the destruction
// operations recalculated.
func (s *Service) destroyOps() ([]txn.Op, error) {
	if s.doc.Life == Dying {
		return nil, errAlreadyDying
	}
	rels, err := s.Relations()
	if err != nil {
		return nil, err
	}
	if len(rels) != s.doc.RelationCount {
		// This is just an early bail out. The relations obtained may still
		// be wrong, but that situation will be caught by a combination of
		// asserts on relationcount and on each known relation, below.
		return nil, errRefresh
	}
	var ops []txn.Op
	removeCount := 0
	for _, rel := range rels {
		relOps, isRemove, err := rel.destroyOps(s.doc.Name)
		if err == errAlreadyDying {
			relOps = []txn.Op{{
				C:      s.st.relations.Name,
				Id:     rel.doc.Key,
				Assert: D{{"life", Dying}},
			}}
		} else if err != nil {
			return nil, err
		}
		if isRemove {
			removeCount++
		}
		ops = append(ops, relOps...)
	}
	// If the service has no units, and all its known relations will be
	// removed, the service can also be removed.
	if s.doc.UnitCount == 0 && s.doc.RelationCount == removeCount {
		hasLastRefs := D{{"life", Alive}, {"unitcount", 0}, {"relationcount", removeCount}}
		return append(ops, s.removeOps(hasLastRefs)...), nil
	}
	// In all other cases, service removal will be handled as a consequence
	// of the removal of the last unit or relation referencing it. It is not
	// simple, and maybe not possible, to build a set of assertions that
	// correctly handle all possible underlying state changes in terms of
	// unit and relation count alone, so we fall back to asserting that the
	// service document has not changed since it was last inspected.
	notLastRefs := D{{"txn-revno", s.doc.TxnRevno}}
	update := D{{"$set", D{{"life", Dying}}}}
	if removeCount != 0 {
		decref := D{{"$inc", D{{"relationcount", -removeCount}}}}
		update = append(update, decref...)
	}
	return append(ops, txn.Op{
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: notLastRefs,
		Update: update,
	}), nil<commit_msg>slightly better checks for service destroy ops<commit_after>package state

import (
	"errors"
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"labix.org/v2/mgo/txn"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/trivial"
	"sort"
	"strconv"
)

// Service represents the state of a service.
type Service struct {
	st  *State
	doc serviceDoc
	annotator
}

// serviceDoc represents the internal state of a service in MongoDB.
type serviceDoc struct {
	Name          string `bson:"_id"`
	CharmURL      *charm.URL
	ForceCharm    bool
	Life          Life
	UnitSeq       int
	UnitCount     int
	RelationCount int
	Exposed       bool
	TxnRevno      int64 `bson:"txn-revno"`
	Annotations   map[string]string
}

func newService(st *State, doc *serviceDoc) *Service {
	svc := &Service{
		st:  st,
		doc: *doc,
		annotator: annotator{
			st:   st,
			coll: st.services.Name,
			id:   doc.Name,
		},
	}
	svc.annotator.annotations = &svc.doc.Annotations
	return svc
}

// Name returns the service name.
func (s *Service) Name() string {
	return s.doc.Name
}

// EntityName returns a name identifying the service that is safe to use
// as a file name.  The returned name will be different from other
// EntityName values returned by any other entities from the same state.
func (s *Service) EntityName() string {
	return "service-" + s.Name()
}

// PasswordValid currently just returns false. Implemented here so that
// a service can be used as an Entity.
func (s *Service) PasswordValid(password string) bool {
	return false
}

// SetPassword currently just returns an error. Implemented here so that
// a service can be used as an Entity.
func (s *Service) SetPassword(password string) error {
	return fmt.Errorf("cannot set password of service")
}

// Annotations returns the service annotations.
func (s *Service) Annotations() map[string]string {
	return s.doc.Annotations
}

// globalKey returns the global database key for the service.
func (s *Service) globalKey() string {
	return "s#" + s.doc.Name
}

// Life returns whether the service is Alive, Dying or Dead.
func (s *Service) Life() Life {
	return s.doc.Life
}

var errRefresh = errors.New("cannot determine relation destruction operations; please refresh the service")

// Destroy ensures that the service and all its relations will be removed at
// some point; if the service has no units, and no relation involving the
// service has any units in scope, they are all removed immediately.
func (s *Service) Destroy() (err error) {
	defer trivial.ErrorContextf(&err, "cannot destroy service %q", s)
	defer func() {
		if err == nil {
			// This is a white lie; the document might actually be removed.
			s.doc.Life = Dying
		}
	}()
	svc := &Service{st: s.st, doc: s.doc}
	for i := 0; i < 5; i++ {
		ops, err := svc.destroyOps()
		switch {
		case err == errRefresh:
		case err == errAlreadyDying:
			return nil
		case err != nil:
			return err
		default:
			if err := svc.st.runner.Run(ops, "", nil); err != txn.ErrAborted {
				return err
			}
		}
		if err := svc.Refresh(); IsNotFound(err) {
			return nil
		} else if err != nil {
			return err
		}
	}
	return ErrExcessiveContention
}

// destroyOps returns the operations required to destroy the service. If it
// returns errRefresh, the service should be refreshed and the destruction
// operations recalculated.
func (s *Service) destroyOps() ([]txn.Op, error) {
	if s.doc.Life == Dying {
		return nil, errAlreadyDying
	}
	rels, err := s.Relations()
	if err != nil {
		return nil, err
	}
	if len(rels) != s.doc.RelationCount {
		// This is just an early bail out. The relations obtained may still
		// be wrong, but that situation will be caught by a combination of
		// asserts on relationcount and on each known relation, below.
		return nil, errRefresh
	}
	var ops []txn.Op
	removeCount := 0
	for _, rel := range rels {
		relOps, isRemove, err := rel.destroyOps(s.doc.Name)
		if err == errAlreadyDying {
			relOps = []txn.Op{{
				C:      s.st.relations.Name,
				Id:     rel.doc.Key,
				Assert: D{{"life", Dying}},
			}}
		} else if err != nil {
			return nil, err
		}
		if isRemove {
			removeCount++
		}
		ops = append(ops, relOps...)
	}
	// If the service has no units, and all its known relations will be
	// removed, the service can also be removed.
	if s.doc.UnitCount == 0 && s.doc.RelationCount == removeCount {
		hasLastRefs := D{{"life", Alive}, {"unitcount", 0}, {"relationcount", removeCount}}
		return append(ops, s.removeOps(hasLastRefs)...), nil
	}
	// In all other cases, service removal will be handled as a consequence
	// of the removal of the last unit or relation referencing it. If any
	// relations have been removed, they'll be caught by the operations
	// collected above; but if any has been added, we need to abort and add
	// a destroy op for that relation too. In combination, it's enough to
	// check for count equality: an add/remove will not touch the count, but
	// will be caught by virtue of being a remove.
	notLastRefs := D{
		{"life", Alive},
		{"relationcount", s.doc.RelationCount},
	}
	// With respect to unit count, a changing value doesn't matter, so long
	// as the count's equality with zero does not change, because all we care
	// about is that *some* unit is, or is not, keeping the service from
	// being removed: the difference between 1 unit and 1000 is irrelevant.
	if s.doc.UnitCount > 0 {
		notLastRefs = append(notLastRefs, D{{"unitcount", D{{"$gt", 0}}}}...)
	} else {
		notLastRefs = append(notLastRefs, D{{"unitcount", 0}}...)
	}
	update := D{{"$set", D{{"life", Dying}}}}
	if removeCount != 0 {
		decref := D{{"$inc", D{{"relationcount", -removeCount}}}}
		update = append(update, decref...)
	}
	return append(ops, txn.Op{
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: notLastRefs,
		Update: update,
	}), nil
{'fraction_non_alphanumeric': 0.11608518350702311, 'fraction_numerical': 0.002718622564567286, 'mean_word_length': 3.2758620689655173, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '4071703', 'n_tokens_mistral': 3800, 'n_tokens_neox': 3544, 'n_words': 1341}