@@ -9,61 +9,54 @@ import (
9
9
"fmt"
10
10
"github.com/gitpod-io/gitpod/common-go/log"
11
11
v1 "github.com/gitpod-io/gitpod/usage-api/v1"
12
+ "github.com/gitpod-io/gitpod/usage/pkg/db"
12
13
"github.com/robfig/cron"
13
14
"google.golang.org/protobuf/types/known/timestamppb"
15
+ "gorm.io/gorm"
14
16
"time"
15
17
)
16
18
17
19
type Job interface {
18
20
Run () error
19
21
}
20
22
21
- func NewLedgerTriggerJobSpec (schedule time.Duration , job Job ) (JobSpec , error ) {
22
- parsed , err := cron .Parse (fmt .Sprintf ("@every %s" , schedule .String ()))
23
+ type JobFunc func () error
24
+
25
+ func (f JobFunc ) Run () error {
26
+ return f ()
27
+ }
28
+
29
+ func NewPeriodicJobSpec (period time.Duration , id string , job Job ) (JobSpec , error ) {
30
+ parsed , err := cron .Parse (fmt .Sprintf ("@every %s" , period .String ()))
23
31
if err != nil {
24
- return JobSpec {}, fmt .Errorf ("failed to parse ledger job schedule: %w" , err )
32
+ return JobSpec {}, fmt .Errorf ("failed to parse period into schedule: %w" , err )
25
33
}
26
34
27
35
return JobSpec {
28
- Job : job ,
29
- ID : "ledger" ,
36
+ Job : WithoutConcurrentRun ( job ) ,
37
+ ID : id ,
30
38
Schedule : parsed ,
31
39
}, nil
32
40
}
33
41
42
+ func NewLedgerTriggerJobSpec (schedule time.Duration , job Job ) (JobSpec , error ) {
43
+ return NewPeriodicJobSpec (schedule , "ledger" , WithoutConcurrentRun (job ))
44
+ }
45
+
34
46
func NewLedgerTrigger (usageClient v1.UsageServiceClient , billingClient v1.BillingServiceClient ) * LedgerJob {
35
47
return & LedgerJob {
36
48
usageClient : usageClient ,
37
49
billingClient : billingClient ,
38
-
39
- running : make (chan struct {}, 1 ),
40
50
}
41
51
}
42
52
43
53
type LedgerJob struct {
44
54
usageClient v1.UsageServiceClient
45
55
billingClient v1.BillingServiceClient
46
-
47
- running chan struct {}
48
56
}
49
57
50
58
func (r * LedgerJob ) Run () (err error ) {
51
59
ctx := context .Background ()
52
-
53
- select {
54
- // attempt a write to signal we want to run
55
- case r .running <- struct {}{}:
56
- // we managed to write, there's no other job executing. Cases are not fall through so we continue executing our main logic.
57
- defer func () {
58
- // signal job completed
59
- <- r .running
60
- }()
61
- default :
62
- // we could not write, so another instance is already running. Skip current run.
63
- log .Infof ("Skipping ledger run, another run is already in progress." )
64
- return nil
65
- }
66
-
67
60
now := time .Now ().UTC ()
68
61
hourAgo := now .Add (- 1 * time .Hour )
69
62
@@ -90,3 +83,62 @@ func (r *LedgerJob) Run() (err error) {
90
83
91
84
return nil
92
85
}
86
+
87
+ // WithoutConcurrentRun wraps a Job and ensures the job does not concurrently
88
+ func WithoutConcurrentRun (j Job ) Job {
89
+ return & preventConcurrentInvocation {
90
+ job : j ,
91
+ running : make (chan struct {}, 1 ),
92
+ }
93
+ }
94
+
95
+ type preventConcurrentInvocation struct {
96
+ job Job
97
+ running chan struct {}
98
+ }
99
+
100
+ func (r * preventConcurrentInvocation ) Run () error {
101
+ select {
102
+ // attempt a write to signal we want to run
103
+ case r .running <- struct {}{}:
104
+ // we managed to write, there's no other job executing. Cases are not fall through so we continue executing our main logic.
105
+ defer func () {
106
+ // signal job completed
107
+ <- r .running
108
+ }()
109
+
110
+ err := r .job .Run ()
111
+ return err
112
+ default :
113
+ // we could not write, so another instance is already running. Skip current run.
114
+ log .Infof ("Job already running, skipping invocation." )
115
+ return nil
116
+ }
117
+ }
118
+
119
+ func NewStoppedWithoutStoppingTimeDetectorSpec (dbconn * gorm.DB ) * StoppedWithoutStoppingTimeDetector {
120
+ return & StoppedWithoutStoppingTimeDetector {
121
+ dbconn : dbconn ,
122
+ }
123
+ }
124
+
125
+ type StoppedWithoutStoppingTimeDetector struct {
126
+ dbconn * gorm.DB
127
+ }
128
+
129
+ func (r * StoppedWithoutStoppingTimeDetector ) Run () error {
130
+ ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Minute )
131
+ defer cancel ()
132
+
133
+ log .Info ("Checking for instances which are stopped but are missing a stoppingTime." )
134
+ instances , err := db .ListWorkspaceInstanceIDsWithPhaseStoppedButNoStoppingTime (ctx , r .dbconn )
135
+ if err != nil {
136
+ log .WithError (err ).Errorf ("Failed to list stop instances without stopping time." )
137
+ return fmt .Errorf ("failed to list instances from db: %w" , err )
138
+ }
139
+
140
+ log .Infof ("Identified %d instances in stopped state without a stopping time." , len (instances ))
141
+ stoppedWithoutStoppingTime .Set (float64 (len (instances )))
142
+
143
+ return nil
144
+ }
0 commit comments