Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package model | 5 package model |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" | 8 "fmt" |
| 9 "time" | 9 "time" |
| 10 | 10 |
| 11 "golang.org/x/net/context" | 11 "golang.org/x/net/context" |
| 12 | 12 |
| 13 » "github.com/luci/luci-go/common/api/dm/service/v1" | 13 » dm "github.com/luci/luci-go/common/api/dm/service/v1" |
| 14 » "github.com/luci/luci-go/common/bit_field" | 14 » bf "github.com/luci/luci-go/common/bit_field" |
| 15 "github.com/luci/luci-go/common/clock" | 15 "github.com/luci/luci-go/common/clock" |
| 16 google_pb "github.com/luci/luci-go/common/proto/google" | 16 google_pb "github.com/luci/luci-go/common/proto/google" |
| 17 ) | 17 ) |
| 18 | 18 |
| 19 // AttemptRetryState indicates the current state of the Attempt's retry | |
| 20 // counters. | |
| 21 type AttemptRetryState struct { | |
| 22 Failed uint32 | |
| 23 Expired uint32 | |
| 24 TimedOut uint32 | |
| 25 Crashed uint32 | |
| 26 } | |
| 27 | |
| 28 // Reset resets all of the AttemptRetryState counters. | |
| 29 func (a *AttemptRetryState) Reset() { | |
| 30 a.Failed = 0 | |
|
dnj (Google)
2016/06/09 18:00:56
*a = AttemptRetryState{} !
iannucci
2016/06/15 00:46:01
mmmm tasty.
| |
| 31 a.Expired = 0 | |
| 32 a.TimedOut = 0 | |
| 33 a.Crashed = 0 | |
| 34 } | |
| 35 | |
| 19 // Attempt is the datastore model for a DM Attempt. It has no parent key, but | 36 // Attempt is the datastore model for a DM Attempt. It has no parent key, but |
| 20 // it may have the following children entities: | 37 // it may have the following children entities: |
| 21 // * FwdDep | 38 // * FwdDep |
| 22 // * AttemptResult | 39 // * AttemptResult |
| 23 // | 40 // |
| 24 // Additionally, every Attempt has an associated BackDepGroup whose ID equals | 41 // Additionally, every Attempt has an associated BackDepGroup whose ID equals |
| 25 // the ID of this Attempt. | 42 // the ID of this Attempt. |
| 26 type Attempt struct { | 43 type Attempt struct { |
| 27 ID dm.Attempt_ID `gae:"$id"` | 44 ID dm.Attempt_ID `gae:"$id"` |
| 28 | 45 |
| 29 Created time.Time | 46 Created time.Time |
| 30 Modified time.Time | 47 Modified time.Time |
| 31 | 48 |
| 32 » State dm.Attempt_State | 49 » State dm.Attempt_State |
| 50 » RetryState AttemptRetryState | |
| 51 | |
| 52 » // Only valid when State == ABNORMAL_FINISHED | |
| 53 » AbnormalFinish dm.AbnormalFinish | |
|
dnj (Google)
2016/06/09 18:00:56
Why not make these pointers?
iannucci
2016/06/15 00:46:01
because luci/gae doesn't allow embed-by-pointer in
| |
| 54 | |
| 55 » // Only valid when State == FINISHED | |
| 56 » ResultExpiration time.Time `gae:",noindex"` | |
| 57 » ResultSize uint32 `gae:",noindex"` | |
| 58 | |
| 59 » // PersistentState is the last successful execution's returned | |
| 60 » // PersistentState. It is set whenever an execution for this Attempt fin ishes | |
| 61 » // sucessfully. This is denormalized with the Execution's | |
| 62 » // ResultPersistentState field. | |
| 63 » PersistentState string `gae:",noindex"` | |
| 33 | 64 |
| 34 // TODO(iannucci): Use CurExecution as a 'deps block version' | 65 // TODO(iannucci): Use CurExecution as a 'deps block version' |
| 35 // then we can have an 'ANY' directive which executes the attempt as soo n | 66 // then we can have an 'ANY' directive which executes the attempt as soo n |
| 36 // as any of the dependencies are ready. If it adds more deps in ANY mod e, | 67 // as any of the dependencies are ready. If it adds more deps in ANY mod e, |
| 37 // the bitmaps get /extended/, and new deps bit indices are added to the | 68 // the bitmaps get /extended/, and new deps bit indices are added to the |
| 38 // existing max. | 69 // existing max. |
| 39 // If it adds more deps in ALL mode, it just converts from ANY -> ALL mo de | 70 // If it adds more deps in ALL mode, it just converts from ANY -> ALL mo de |
| 40 // and follows the current behavior. | 71 // and follows the current behavior. |
| 41 | 72 |
| 42 // CurExecution is the maximum Execution ID for this Attempt so far. Exe cution | 73 // CurExecution is the maximum Execution ID for this Attempt so far. Exe cution |
| 43 // IDs are contiguous from [1, CurExecution]. If the State is not curren tly | 74 // IDs are contiguous from [1, CurExecution]. If the State is not curren tly |
| 44 // Executing, then CurExecution represents the execution that JUST finis hed | 75 // Executing, then CurExecution represents the execution that JUST finis hed |
| 45 // (or 0 if no Executions have been made yet). | 76 // (or 0 if no Executions have been made yet). |
| 46 CurExecution uint32 | 77 CurExecution uint32 |
| 47 | 78 |
| 48 » // AddingDepsBitmap is valid only while Attempt is in 'AddingDeps'. | 79 » // DepMap is valid only while Attempt is in a State of EXECUTING or WAIT ING. |
| 49 » // A field value of 0 means the backdep hasn't been added yet. | 80 » // |
| 50 » AddingDepsBitmap bf.BitField `gae:",noindex" json:"-"` | 81 » // The size of this field is inspected to deteremine what the next state after |
| 51 | 82 » // EXECUTING is. If the size == 0, it means the Attempt should move to t he |
| 52 » // WaitingDepBitmap is valid only while Attempt is in a Status of 'Addin gDeps' | 83 » // FINISHED state. Otherwise it means that the Attempt should move to th e |
| 53 » // or 'Blocked'. | 84 » // WAITING state. |
| 54 » // A field value of 0 means that the dep is currently waiting. | 85 » // |
| 55 » WaitingDepBitmap bf.BitField `gae:",noindex" json:"-"` | 86 » // A bit field value of 0 means that the dep is currently waiting, and a bit |
| 56 | 87 » // value of 1 means that the coresponding dep is satisfined. The Attempt can |
| 57 » // Only valid while Attempt is Finished | 88 » // be unblocked from WAITING back to SCHEDULING when all bits are set to 1. |
| 58 » ResultExpiration time.Time | 89 » DepMap bf.BitField `gae:",noindex" json:"-"` |
|
iannucci
2016/06/08 02:54:24
this is part of the state machine simplification;
| |
| 59 » ResultSize uint32 | |
| 60 | 90 |
| 61 // A lazily-updated boolean to reflect that this Attempt is expired for | 91 // A lazily-updated boolean to reflect that this Attempt is expired for |
| 62 // queries. | 92 // queries. |
| 63 » Expired bool | 93 » ResultExpired bool |
| 64 } | 94 } |
| 65 | 95 |
| 66 // MakeAttempt is a convenience function to create a new Attempt model in | 96 // MakeAttempt is a convenience function to create a new Attempt model in |
| 67 // the NeedsExecution state. | 97 // the NeedsExecution state. |
| 68 func MakeAttempt(c context.Context, aid *dm.Attempt_ID) *Attempt { | 98 func MakeAttempt(c context.Context, aid *dm.Attempt_ID) *Attempt { |
| 69 now := clock.Now(c).UTC() | 99 now := clock.Now(c).UTC() |
| 70 return &Attempt{ | 100 return &Attempt{ |
| 71 ID: *aid, | 101 ID: *aid, |
| 72 Created: now, | 102 Created: now, |
| 73 Modified: now, | 103 Modified: now, |
| 74 } | 104 } |
| 75 } | 105 } |
| 76 | 106 |
| 77 // ModifyState changes the current state of this Attempt and updates its | 107 // ModifyState changes the current state of this Attempt and updates its |
| 78 // Modified timestamp. | 108 // Modified timestamp. |
| 79 func (a *Attempt) ModifyState(c context.Context, newState dm.Attempt_State) erro r { | 109 func (a *Attempt) ModifyState(c context.Context, newState dm.Attempt_State) erro r { |
| 110 if a.State == newState { | |
| 111 return nil | |
| 112 } | |
| 80 if err := a.State.Evolve(newState); err != nil { | 113 if err := a.State.Evolve(newState); err != nil { |
| 81 return err | 114 return err |
| 82 } | 115 } |
| 83 now := clock.Now(c).UTC() | 116 now := clock.Now(c).UTC() |
| 84 if now.After(a.Modified) { | 117 if now.After(a.Modified) { |
| 85 a.Modified = now | 118 a.Modified = now |
| 86 } else { | 119 } else { |
| 87 // Microsecond is the smallest granularity that datastore can st ore | 120 // Microsecond is the smallest granularity that datastore can st ore |
| 88 // timestamps, so use that to disambiguate: the goal here is tha t any | 121 // timestamps, so use that to disambiguate: the goal here is tha t any |
| 89 // modification always increments the modified time, and never d ecrements | 122 // modification always increments the modified time, and never d ecrements |
| 90 // it. | 123 // it. |
| 91 a.Modified = a.Modified.Add(time.Microsecond) | 124 a.Modified = a.Modified.Add(time.Microsecond) |
| 92 } | 125 } |
| 93 return nil | 126 return nil |
| 94 } | 127 } |
| 95 | 128 |
| 96 // MustModifyState is the same as ModifyState, except that it panics if the | |
| 97 // state transition is invalid. | |
| 98 func (a *Attempt) MustModifyState(c context.Context, newState dm.Attempt_State) { | |
| 99 err := a.ModifyState(c, newState) | |
| 100 if err != nil { | |
| 101 panic(err) | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 // ToProto returns a dm proto version of this Attempt. | 129 // ToProto returns a dm proto version of this Attempt. |
| 106 func (a *Attempt) ToProto(withData bool) *dm.Attempt { | 130 func (a *Attempt) ToProto(withData bool) *dm.Attempt { |
| 107 ret := dm.Attempt{Id: &a.ID} | 131 ret := dm.Attempt{Id: &a.ID} |
| 108 if withData { | 132 if withData { |
| 109 ret.Data = a.DataProto() | 133 ret.Data = a.DataProto() |
| 110 } | 134 } |
| 111 return &ret | 135 return &ret |
| 112 } | 136 } |
| 113 | 137 |
| 114 // DataProto returns an Attempt.Data message for this Attempt. | 138 // DataProto returns an Attempt.Data message for this Attempt. |
| 115 func (a *Attempt) DataProto() *dm.Attempt_Data { | 139 func (a *Attempt) DataProto() (ret *dm.Attempt_Data) { |
| 116 » ret := (*dm.Attempt_Data)(nil) | |
| 117 switch a.State { | 140 switch a.State { |
| 118 » case dm.Attempt_NEEDS_EXECUTION: | 141 » case dm.Attempt_SCHEDULING: |
|
iannucci
2016/06/08 02:54:24
these changes are due to the state machine simplif
| |
| 119 » » ret = dm.NewAttemptNeedsExecution(a.Modified).Data | 142 » » ret = dm.NewAttemptScheduling().Data |
| 120 | |
| 121 case dm.Attempt_EXECUTING: | 143 case dm.Attempt_EXECUTING: |
| 122 ret = dm.NewAttemptExecuting(a.CurExecution).Data | 144 ret = dm.NewAttemptExecuting(a.CurExecution).Data |
| 123 | 145 » case dm.Attempt_WAITING: |
| 124 » case dm.Attempt_ADDING_DEPS: | 146 » » ret = dm.NewAttemptWaiting(a.DepMap.Size() - a.DepMap.CountSet() ).Data |
| 125 » » addset := a.AddingDepsBitmap | |
| 126 » » waitset := a.WaitingDepBitmap | |
| 127 » » setlen := addset.Size() | |
| 128 | |
| 129 » » ret = dm.NewAttemptAddingDeps( | |
| 130 » » » setlen-addset.CountSet(), setlen-waitset.CountSet()).Dat a | |
| 131 | |
| 132 » case dm.Attempt_BLOCKED: | |
| 133 » » waitset := a.WaitingDepBitmap | |
| 134 » » setlen := waitset.Size() | |
| 135 | |
| 136 » » ret = dm.NewAttemptBlocked(setlen - waitset.CountSet()).Data | |
| 137 | |
| 138 case dm.Attempt_FINISHED: | 147 case dm.Attempt_FINISHED: |
| 139 » » ret = dm.NewAttemptFinished(a.ResultExpiration, a.ResultSize, "" ).Data | 148 » » ret = dm.NewAttemptFinished(a.ResultExpiration, a.ResultSize, "" , |
| 140 | 149 » » » string(a.PersistentState)).Data |
| 150 » case dm.Attempt_ABNORMAL_FINISHED: | |
| 151 » » ret = dm.NewAttemptAbnormalFinish(&a.AbnormalFinish).Data | |
| 141 default: | 152 default: |
| 142 panic(fmt.Errorf("unknown Attempt_State: %s", a.State)) | 153 panic(fmt.Errorf("unknown Attempt_State: %s", a.State)) |
| 143 } | 154 } |
| 144 ret.Created = google_pb.NewTimestamp(a.Created) | 155 ret.Created = google_pb.NewTimestamp(a.Created) |
| 145 ret.Modified = google_pb.NewTimestamp(a.Modified) | 156 ret.Modified = google_pb.NewTimestamp(a.Modified) |
| 146 ret.NumExecutions = a.CurExecution | 157 ret.NumExecutions = a.CurExecution |
| 147 return ret | 158 return ret |
| 148 } | 159 } |
| OLD | NEW |