-
Notifications
You must be signed in to change notification settings - Fork 0
Improved cron syntax #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1790041
238932a
8ad8808
8101d65
67ffbe4
fef32ad
d4fe8f0
af62323
5fec0ff
e93cf15
80c7c1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| package application | ||
|
|
||
| // Domain describes a domain module that exposes its repository. | ||
| type Domain interface { | ||
| GetRepository() any | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,14 +5,19 @@ import ( | |||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // ServiceStatus represents the lifecycle state of a service. | ||||||||||||||||||||||||||||||||||||
| type ServiceStatus string | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const ( | ||||||||||||||||||||||||||||||||||||
| // ServiceStatusNotStarted indicates service has not started yet. | ||||||||||||||||||||||||||||||||||||
| ServiceStatusNotStarted ServiceStatus = "NOT_STARTED" | ||||||||||||||||||||||||||||||||||||
| ServiceStatusStarted ServiceStatus = "STARTED" | ||||||||||||||||||||||||||||||||||||
| ServiceStatusError ServiceStatus = "ERROR" | ||||||||||||||||||||||||||||||||||||
| // ServiceStatusStarted indicates service is currently running. | ||||||||||||||||||||||||||||||||||||
| ServiceStatusStarted ServiceStatus = "STARTED" | ||||||||||||||||||||||||||||||||||||
| // ServiceStatusError indicates service finished with an error. | ||||||||||||||||||||||||||||||||||||
| ServiceStatusError ServiceStatus = "ERROR" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // ServiceHealth contains health information for a single service. | ||||||||||||||||||||||||||||||||||||
| type ServiceHealth struct { | ||||||||||||||||||||||||||||||||||||
| Status ServiceStatus `json:"status"` | ||||||||||||||||||||||||||||||||||||
| StartedAt *time.Time `json:"startedAt"` | ||||||||||||||||||||||||||||||||||||
|
|
@@ -21,16 +26,19 @@ type ServiceHealth struct { | |||||||||||||||||||||||||||||||||||
| Data any `json:"data,omitempty"` | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| type ApplicationHealth struct { | ||||||||||||||||||||||||||||||||||||
| // Health contains overall application health and service states. | ||||||||||||||||||||||||||||||||||||
| type Health struct { | ||||||||||||||||||||||||||||||||||||
| StartedAt time.Time `json:"startedAt"` | ||||||||||||||||||||||||||||||||||||
| Services map[string]*ServiceHealth `json:"services"` | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+29
to
33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's find and inspect the health.go file
find . -name "health.go" -type fRepository: platforma-dev/platforma Length of output: 90 🏁 Script executed: # Let's examine the Health struct and related methods
cat -n application/health.go | head -100Repository: platforma-dev/platforma Length of output: 2928 🏁 Script executed: # Search for calls to the Health methods to understand concurrent access patterns
rg "StartService|FailService|SetServiceData|String" --type go -B 2 -A 2 -iRepository: platforma-dev/platforma Length of output: 40513 🏁 Script executed: # Also search for service runners and goroutine patterns
rg "go " --type go | grep -E "Service|Runner|health"Repository: platforma-dev/platforma Length of output: 49 🏁 Script executed: # Check the service launching code in application.go
sed -n '130,160p' application/application.goRepository: platforma-dev/platforma Length of output: 938 🏁 Script executed: # Look for all places where health methods are called
rg "health\.(StartService|FailService|SetServiceData|String)" --type go -B 2 -A 2Repository: platforma-dev/platforma Length of output: 953 🏁 Script executed: # Verify the exact line numbers for all methods
sed -n '40,82p' application/health.go | cat -nRepository: platforma-dev/platforma Length of output: 1459 Guard Multiple service goroutines call Suggested fix (mutex + locking) import (
"encoding/json"
+ "sync"
"time"
)
@@
type Health struct {
+ mu sync.RWMutex
StartedAt time.Time `json:"startedAt"`
Services map[string]*ServiceHealth `json:"services"`
}
@@
func (h *Health) StartService(serviceName string) {
+ h.mu.Lock()
+ defer h.mu.Unlock()
if service, ok := h.Services[serviceName]; ok {
service.Status = ServiceStatusStarted
@@
func (h *Health) FailService(serviceName string, err error) {
+ h.mu.Lock()
+ defer h.mu.Unlock()
if service, ok := h.Services[serviceName]; ok {
@@
func (h *Health) SetServiceData(serviceName string, data any) {
+ h.mu.Lock()
+ defer h.mu.Unlock()
if service, ok := h.Services[serviceName]; ok {
service.Data = data
h.Services[serviceName] = service
}
}
@@
func (h *Health) String() string {
+ h.mu.RLock()
+ defer h.mu.RUnlock()
b, _ := json.Marshal(h)
return string(b)
}
@@
func (h *Health) StartApplication() {
+ h.mu.Lock()
+ defer h.mu.Unlock()
h.StartedAt = time.Now()
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| func NewApplicationHealth() *ApplicationHealth { | ||||||||||||||||||||||||||||||||||||
| return &ApplicationHealth{Services: make(map[string]*ServiceHealth)} | ||||||||||||||||||||||||||||||||||||
| // NewHealth creates an ApplicationHealth with initialized storage. | ||||||||||||||||||||||||||||||||||||
| func NewHealth() *Health { | ||||||||||||||||||||||||||||||||||||
| return &Health{Services: make(map[string]*ServiceHealth)} | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update doc comment to reference The comment still mentions ✏️ Suggested fix-// NewHealth creates an ApplicationHealth with initialized storage.
+// NewHealth creates a Health with initialized storage.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| func (h *ApplicationHealth) StartService(serviceName string) { | ||||||||||||||||||||||||||||||||||||
| // StartService marks the given service as started and stores start time. | ||||||||||||||||||||||||||||||||||||
| func (h *Health) StartService(serviceName string) { | ||||||||||||||||||||||||||||||||||||
| if service, ok := h.Services[serviceName]; ok { | ||||||||||||||||||||||||||||||||||||
| service.Status = ServiceStatusStarted | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
@@ -41,7 +49,8 @@ func (h *ApplicationHealth) StartService(serviceName string) { | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| func (h *ApplicationHealth) FailService(serviceName string, err error) { | ||||||||||||||||||||||||||||||||||||
| // FailService marks the given service as failed and stores the error. | ||||||||||||||||||||||||||||||||||||
| func (h *Health) FailService(serviceName string, err error) { | ||||||||||||||||||||||||||||||||||||
| if service, ok := h.Services[serviceName]; ok { | ||||||||||||||||||||||||||||||||||||
| service.Status = ServiceStatusError | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
@@ -54,18 +63,20 @@ func (h *ApplicationHealth) FailService(serviceName string, err error) { | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| func (h *ApplicationHealth) SetServiceData(serviceName string, data any) { | ||||||||||||||||||||||||||||||||||||
| // SetServiceData stores additional health payload for the given service. | ||||||||||||||||||||||||||||||||||||
| func (h *Health) SetServiceData(serviceName string, data any) { | ||||||||||||||||||||||||||||||||||||
| if service, ok := h.Services[serviceName]; ok { | ||||||||||||||||||||||||||||||||||||
| service.Data = data | ||||||||||||||||||||||||||||||||||||
| h.Services[serviceName] = service | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| func (h *ApplicationHealth) String() string { | ||||||||||||||||||||||||||||||||||||
| func (h *Health) String() string { | ||||||||||||||||||||||||||||||||||||
| b, _ := json.Marshal(h) | ||||||||||||||||||||||||||||||||||||
| return string(b) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| func (h *ApplicationHealth) StartApplication() { | ||||||||||||||||||||||||||||||||||||
| // StartApplication marks application start time. | ||||||||||||||||||||||||||||||||||||
| func (h *Health) StartApplication() { | ||||||||||||||||||||||||||||||||||||
| h.StartedAt = time.Now() | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "time" | ||
|
|
||
| "github.com/platforma-dev/platforma/application" | ||
| "github.com/platforma-dev/platforma/log" | ||
| "github.com/platforma-dev/platforma/scheduler" | ||
| ) | ||
|
|
||
| func dailyBackup(ctx context.Context) error { | ||
| log.InfoContext(ctx, "executing daily backup task") | ||
| return nil | ||
| } | ||
|
|
||
| func weekdayReport(ctx context.Context) error { | ||
| log.InfoContext(ctx, "generating weekday report") | ||
| return nil | ||
| } | ||
|
|
||
| func frequentHealthCheck(ctx context.Context) error { | ||
| log.InfoContext(ctx, "performing health check") | ||
| return nil | ||
| } | ||
|
|
||
| func main() { | ||
| ctx, cancel := context.WithCancel(context.Background()) | ||
| defer cancel() | ||
|
|
||
| // Example 1: Using @every syntax - every 5 seconds | ||
| s1, err := scheduler.New("@every 5s", application.RunnerFunc(func(ctx context.Context) error { | ||
| log.InfoContext(ctx, "@every syntax: every 5 seconds") | ||
| return nil | ||
| })) | ||
| if err != nil { | ||
| log.ErrorContext(ctx, "failed to create scheduler 1", "error", err) | ||
| return | ||
| } | ||
|
|
||
| // Example 2: Using @every syntax - every 3 seconds | ||
| s2, err := scheduler.New("@every 3s", application.RunnerFunc(func(ctx context.Context) error { | ||
| log.InfoContext(ctx, "@every syntax: every 3 seconds") | ||
| return nil | ||
| })) | ||
| if err != nil { | ||
| log.ErrorContext(ctx, "failed to create scheduler 2", "error", err) | ||
| return | ||
| } | ||
|
|
||
| // Example 3: Daily task (would run at midnight, but won't execute in this demo) | ||
| s3, err := scheduler.New("@daily", application.RunnerFunc(dailyBackup)) | ||
| if err != nil { | ||
| log.ErrorContext(ctx, "failed to create scheduler 3", "error", err) | ||
| return | ||
| } | ||
|
|
||
| // Example 4: Weekday task (would run at 9 AM on weekdays, won't execute in this demo) | ||
| s4, err := scheduler.New("0 9 * * MON-FRI", application.RunnerFunc(weekdayReport)) | ||
| if err != nil { | ||
| log.ErrorContext(ctx, "failed to create scheduler 4", "error", err) | ||
| return | ||
| } | ||
|
|
||
| // Example 5: Hourly task (won't execute in this demo) | ||
| s5, err := scheduler.New("@hourly", application.RunnerFunc(frequentHealthCheck)) | ||
| if err != nil { | ||
| log.ErrorContext(ctx, "failed to create scheduler 5", "error", err) | ||
| return | ||
| } | ||
|
|
||
| fmt.Println("Starting cron scheduler demo...") | ||
| fmt.Println("Active schedulers:") | ||
| fmt.Println(" 1. Every 5 seconds (@every 5s)") | ||
| fmt.Println(" 2. Every 3 seconds (@every 3s)") | ||
| fmt.Println(" 3. Daily at midnight (@daily) - won't execute in demo") | ||
| fmt.Println(" 4. Weekdays at 9 AM (0 9 * * MON-FRI) - won't execute in demo") | ||
| fmt.Println(" 5. Hourly (@hourly) - won't execute in demo") | ||
| fmt.Println() | ||
| fmt.Println("Watch the logs for executions. Demo will run for 15 seconds.") | ||
| fmt.Println() | ||
|
|
||
| // Start all schedulers in background | ||
| go s1.Run(ctx) | ||
| go s2.Run(ctx) | ||
| go s3.Run(ctx) | ||
| go s4.Run(ctx) | ||
| go s5.Run(ctx) | ||
|
|
||
| // Run for 15 seconds to demonstrate the frequent tasks | ||
| time.Sleep(15 * time.Second) | ||
| cancel() | ||
|
|
||
| // Allow graceful shutdown | ||
| time.Sleep(100 * time.Millisecond) | ||
|
|
||
| fmt.Println() | ||
| fmt.Println("Demo completed!") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
golangci-lint exclusion rules path regex pattern matching semantics💡 Result:
path/path-exceptare regular expressions matched against the issue’s file path string (not glob patterns). Examples in the docs use regexes like(.+)_test\.go/_test\.go. [1]^...$if you need a full-string match (this follows Go’s regexp “match anywhere unless you anchor” behavior). [1]run.relative-path-mode(this setting is used by exclusions):gomod(relative to thego.moddir),gitroot(relative to repo root),cfg(relative to config file), orwd(relative to where you rungolangci-lint). [2]/is replaced with the OS-specific path separator so configs work on Windows. [3]pathmatching was described as relative to the current working directory, and--path-prefixcould be used to extend paths before matching. [4]Sources: [1] [2] [3] [4] [5]
Anchor the revive exclusion regex to the path root to prevent unintended matches.
(session|auth)/*is unanchored and will match the substrings "session" or "auth" anywhere in the file path. According to golangci-lint's regex semantics (which follow Go's regexp behavior), patterns must use^...$anchors for full-string matching. This means paths likedocs/authors/something.gowould be unintentionally excluded since they contain "auth" as a substring.Change to
^(session|auth)(/|$)to match only these directories at the root level:🔧 Suggested fix
🤖 Prompt for AI Agents