| 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 = AttemptRetryState{} |
| 31 } |
| 32 |
| 19 // Attempt is the datastore model for a DM Attempt. It has no parent key, but | 33 // Attempt is the datastore model for a DM Attempt. It has no parent key, but |
| 20 // it may have the following children entities: | 34 // it may have the following children entities: |
| 21 // * FwdDep | 35 // * FwdDep |
| 22 // * AttemptResult | 36 // * AttemptResult |
| 23 // | 37 // |
| 24 // Additionally, every Attempt has an associated BackDepGroup whose ID equals | 38 // Additionally, every Attempt has an associated BackDepGroup whose ID equals |
| 25 // the ID of this Attempt. | 39 // the ID of this Attempt. |
| 26 type Attempt struct { | 40 type Attempt struct { |
| 27 ID dm.Attempt_ID `gae:"$id"` | 41 ID dm.Attempt_ID `gae:"$id"` |
| 28 | 42 |
| 29 Created time.Time | 43 Created time.Time |
| 30 Modified time.Time | 44 Modified time.Time |
| 31 | 45 |
| 32 » State dm.Attempt_State | 46 » State dm.Attempt_State |
| 47 » RetryState AttemptRetryState |
| 48 |
| 49 » // Only valid when State == ABNORMAL_FINISHED |
| 50 » AbnormalFinish dm.AbnormalFinish |
| 51 |
| 52 » // Only valid when State == FINISHED |
| 53 » ResultExpiration time.Time `gae:",noindex"` |
| 54 » ResultSize uint32 `gae:",noindex"` |
| 55 |
| 56 » // PersistentState is the last successful execution's returned |
| 57 » // PersistentState. It is set whenever an execution for this Attempt fin
ishes |
| 58 » // sucessfully. This is denormalized with the Execution's |
| 59 » // ResultPersistentState field. |
| 60 » PersistentState []byte `gae:",noindex"` |
| 33 | 61 |
| 34 // TODO(iannucci): Use CurExecution as a 'deps block version' | 62 // TODO(iannucci): Use CurExecution as a 'deps block version' |
| 35 // then we can have an 'ANY' directive which executes the attempt as soo
n | 63 // 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, | 64 // 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 | 65 // the bitmaps get /extended/, and new deps bit indices are added to the |
| 38 // existing max. | 66 // existing max. |
| 39 // If it adds more deps in ALL mode, it just converts from ANY -> ALL mo
de | 67 // If it adds more deps in ALL mode, it just converts from ANY -> ALL mo
de |
| 40 // and follows the current behavior. | 68 // and follows the current behavior. |
| 41 | 69 |
| 42 // CurExecution is the maximum Execution ID for this Attempt so far. Exe
cution | 70 // 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 | 71 // IDs are contiguous from [1, CurExecution]. If the State is not curren
tly |
| 44 // Executing, then CurExecution represents the execution that JUST finis
hed | 72 // Executing, then CurExecution represents the execution that JUST finis
hed |
| 45 // (or 0 if no Executions have been made yet). | 73 // (or 0 if no Executions have been made yet). |
| 46 CurExecution uint32 | 74 CurExecution uint32 |
| 47 | 75 |
| 48 » // AddingDepsBitmap is valid only while Attempt is in 'AddingDeps'. | 76 » // 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. | 77 » // |
| 50 » AddingDepsBitmap bf.BitField `gae:",noindex" json:"-"` | 78 » // The size of this field is inspected to deteremine what the next state
after |
| 51 | 79 » // 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' | 80 » // FINISHED state. Otherwise it means that the Attempt should move to th
e |
| 53 » // or 'Blocked'. | 81 » // WAITING state. |
| 54 » // A field value of 0 means that the dep is currently waiting. | 82 » // |
| 55 » WaitingDepBitmap bf.BitField `gae:",noindex" json:"-"` | 83 » // A bit field value of 0 means that the dep is currently waiting, and a
bit |
| 56 | 84 » // value of 1 means that the coresponding dep is satisfined. The Attempt
can |
| 57 » // Only valid while Attempt is Finished | 85 » // be unblocked from WAITING back to SCHEDULING when all bits are set to
1. |
| 58 » ResultExpiration time.Time | 86 » DepMap bf.BitField `gae:",noindex" json:"-"` |
| 59 » ResultSize uint32 | |
| 60 | 87 |
| 61 // A lazily-updated boolean to reflect that this Attempt is expired for | 88 // A lazily-updated boolean to reflect that this Attempt is expired for |
| 62 // queries. | 89 // queries. |
| 63 » Expired bool | 90 » ResultExpired bool |
| 64 } | 91 } |
| 65 | 92 |
| 66 // MakeAttempt is a convenience function to create a new Attempt model in | 93 // MakeAttempt is a convenience function to create a new Attempt model in |
| 67 // the NeedsExecution state. | 94 // the NeedsExecution state. |
| 68 func MakeAttempt(c context.Context, aid *dm.Attempt_ID) *Attempt { | 95 func MakeAttempt(c context.Context, aid *dm.Attempt_ID) *Attempt { |
| 69 now := clock.Now(c).UTC() | 96 now := clock.Now(c).UTC() |
| 70 return &Attempt{ | 97 return &Attempt{ |
| 71 ID: *aid, | 98 ID: *aid, |
| 72 Created: now, | 99 Created: now, |
| 73 Modified: now, | 100 Modified: now, |
| 74 } | 101 } |
| 75 } | 102 } |
| 76 | 103 |
| 77 // ModifyState changes the current state of this Attempt and updates its | 104 // ModifyState changes the current state of this Attempt and updates its |
| 78 // Modified timestamp. | 105 // Modified timestamp. |
| 79 func (a *Attempt) ModifyState(c context.Context, newState dm.Attempt_State) erro
r { | 106 func (a *Attempt) ModifyState(c context.Context, newState dm.Attempt_State) erro
r { |
| 107 if a.State == newState { |
| 108 return nil |
| 109 } |
| 80 if err := a.State.Evolve(newState); err != nil { | 110 if err := a.State.Evolve(newState); err != nil { |
| 81 return err | 111 return err |
| 82 } | 112 } |
| 83 now := clock.Now(c).UTC() | 113 now := clock.Now(c).UTC() |
| 84 if now.After(a.Modified) { | 114 if now.After(a.Modified) { |
| 85 a.Modified = now | 115 a.Modified = now |
| 86 } else { | 116 } else { |
| 87 // Microsecond is the smallest granularity that datastore can st
ore | 117 // Microsecond is the smallest granularity that datastore can st
ore |
| 88 // timestamps, so use that to disambiguate: the goal here is tha
t any | 118 // timestamps, so use that to disambiguate: the goal here is tha
t any |
| 89 // modification always increments the modified time, and never d
ecrements | 119 // modification always increments the modified time, and never d
ecrements |
| 90 // it. | 120 // it. |
| 91 a.Modified = a.Modified.Add(time.Microsecond) | 121 a.Modified = a.Modified.Add(time.Microsecond) |
| 92 } | 122 } |
| 93 return nil | 123 return nil |
| 94 } | 124 } |
| 95 | 125 |
| 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. | 126 // ToProto returns a dm proto version of this Attempt. |
| 106 func (a *Attempt) ToProto(withData bool) *dm.Attempt { | 127 func (a *Attempt) ToProto(withData bool) *dm.Attempt { |
| 107 ret := dm.Attempt{Id: &a.ID} | 128 ret := dm.Attempt{Id: &a.ID} |
| 108 if withData { | 129 if withData { |
| 109 ret.Data = a.DataProto() | 130 ret.Data = a.DataProto() |
| 110 } | 131 } |
| 111 return &ret | 132 return &ret |
| 112 } | 133 } |
| 113 | 134 |
| 114 // DataProto returns an Attempt.Data message for this Attempt. | 135 // DataProto returns an Attempt.Data message for this Attempt. |
| 115 func (a *Attempt) DataProto() *dm.Attempt_Data { | 136 func (a *Attempt) DataProto() (ret *dm.Attempt_Data) { |
| 116 » ret := (*dm.Attempt_Data)(nil) | |
| 117 switch a.State { | 137 switch a.State { |
| 118 » case dm.Attempt_NEEDS_EXECUTION: | 138 » case dm.Attempt_SCHEDULING: |
| 119 » » ret = dm.NewAttemptNeedsExecution(a.Modified).Data | 139 » » ret = dm.NewAttemptScheduling().Data |
| 120 | |
| 121 case dm.Attempt_EXECUTING: | 140 case dm.Attempt_EXECUTING: |
| 122 ret = dm.NewAttemptExecuting(a.CurExecution).Data | 141 ret = dm.NewAttemptExecuting(a.CurExecution).Data |
| 123 | 142 » case dm.Attempt_WAITING: |
| 124 » case dm.Attempt_ADDING_DEPS: | 143 » » 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: | 144 case dm.Attempt_FINISHED: |
| 139 » » ret = dm.NewAttemptFinished(a.ResultExpiration, a.ResultSize, ""
).Data | 145 » » ret = dm.NewAttemptFinished(a.ResultExpiration, a.ResultSize, ""
, |
| 140 | 146 » » » a.PersistentState).Data |
| 147 » case dm.Attempt_ABNORMAL_FINISHED: |
| 148 » » ret = dm.NewAttemptAbnormalFinish(&a.AbnormalFinish).Data |
| 141 default: | 149 default: |
| 142 panic(fmt.Errorf("unknown Attempt_State: %s", a.State)) | 150 panic(fmt.Errorf("unknown Attempt_State: %s", a.State)) |
| 143 } | 151 } |
| 144 ret.Created = google_pb.NewTimestamp(a.Created) | 152 ret.Created = google_pb.NewTimestamp(a.Created) |
| 145 ret.Modified = google_pb.NewTimestamp(a.Modified) | 153 ret.Modified = google_pb.NewTimestamp(a.Modified) |
| 146 ret.NumExecutions = a.CurExecution | 154 ret.NumExecutions = a.CurExecution |
| 147 return ret | 155 return ret |
| 148 } | 156 } |
| OLD | NEW |