feat: [#783] Add app.Run(...runner) function to optimize the app running process#1221
feat: [#783] Add app.Run(...runner) function to optimize the app running process#1221
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1221 +/- ##
==========================================
- Coverage 66.66% 65.95% -0.71%
==========================================
Files 237 243 +6
Lines 15879 16676 +797
==========================================
+ Hits 10585 10998 +413
- Misses 4928 5298 +370
- Partials 366 380 +14 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.
| Benchmark suite | Current: 968517a | Previous: e78715c | Ratio |
|---|---|---|---|
BenchmarkFile_ReadWrite |
357473 ns/op 6257 B/op 99 allocs/op |
198025 ns/op 6257 B/op 99 allocs/op |
1.81 |
BenchmarkFile_ReadWrite - ns/op |
357473 ns/op |
198025 ns/op |
1.81 |
This comment was automatically generated by workflow using github-action-benchmark.
| return App | ||
| } | ||
|
|
||
| func (r *Application) About(section string, items []foundation.AboutItem) { |
There was a problem hiding this comment.
Sort functions, no changes.
foundation/application.go
Outdated
| path = append([]string{support.RelativePath, "resources"}, path...) | ||
| return r.absPath(path...) | ||
| } | ||
| func (r *Application) Run(modules ...foundation.Module) { |
| } | ||
|
|
||
| func TestApplicationTestSuite(t *testing.T) { | ||
| assert.Nil(t, file.PutContent(support.EnvFilePath, "APP_KEY=12345678901234567890123456789012")) |
There was a problem hiding this comment.
Pull Request Overview
This PR adds a new Run function to the Application interface that optimizes the app running process by launching Route, Grpc, Queue, and Schedule modules concurrently. It also introduces a Context function to handle graceful shutdown via signal listening.
- Adds
Run(modules ...foundation.Module)method to automatically start specified modules or auto-detect based on configuration - Adds
Context()method to expose the application's context for shutdown coordination - Implements graceful shutdown handling with signal capture and context cancellation
Reviewed Changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| contracts/foundation/application.go | Defines Module type and adds Context/Run method signatures to Application interface |
| foundation/application.go | Implements new Run and Context methods with module constants and graceful shutdown logic |
| foundation/application_test.go | Adds comprehensive test coverage for the new Run functionality with mocked dependencies |
| foundation/.env | Adds test environment file with APP_KEY for testing |
| mocks/foundation/Application.go | Updates mock implementation with new Context and Run method stubs |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
Hey @goravel/core-developers Could you help confirm this and another PRs? The initiation framework process is changed in these two PRs totally. We can have a discussion on this. |
| @@ -1,4 +1,3 @@ | |||
| .env | |||
There was a problem hiding this comment.
Added a .env file for testing (foundation/.env), it's hard to test the code if no .env due to the init function.
There was a problem hiding this comment.
We can export the APP_KEY as a GitHub variable, and it can then be exported into the terminal?
There was a problem hiding this comment.
Added a
.envfile for testing (foundation/.env), it's hard to test the code if no .env due to theinitfunction.
Perhaps consider changing .env to /.env in the .gitignore file to ignore only the .env file in the root directory.
There was a problem hiding this comment.
We can export the APP_KEY as a GitHub variable, and it can then be exported into the terminal?
This way can't test the foundation package locally.
Perhaps consider changing .env to /.env in the .gitignore file to ignore only the .env file in the root directory.
It's the goravel/framework repo, so it doesn't matter .env or /.env.
foundation/application.go
Outdated
| return r.absPath(path...) | ||
| } | ||
| func (r *Application) Run(modules ...foundation.Module) { | ||
| signal.Notify(r.quit, syscall.SIGINT, syscall.SIGTERM) |
There was a problem hiding this comment.
We can use signal.NotifyContext instead of keeping r.quit and r.cancel as a struct-level field.
foundation/application.go
Outdated
| modules = append(modules, Queue) | ||
| } | ||
|
|
||
| schedule := r.MakeSchedule() |
There was a problem hiding this comment.
Why are we starting scheduling by default? Shouldn’t we enable only the bare minimum here?
There was a problem hiding this comment.
No, the schedule will only be run when schedule != nil && len(schedule.Events()) > 0.
foundation/application.go
Outdated
| artisanFacade.Register(commands) | ||
| } | ||
|
|
||
| func (r *Application) runRoute() { |
There was a problem hiding this comment.
The logic for runRoute, runGrpc, runQueue, and runSchedule looks similar. Maybe we can create a reusable method that just takes the Run and Shutdown functions as parameters?
There was a problem hiding this comment.
It's a bit hard to do that. Run functions are different: Run(host ...string) error, Run() error.
There was a problem hiding this comment.
Yeah, that’s why you shouldn’t call it directly instead, wrap it(but not sure if it’s worth it). (though it’s just a subset of @almas-x ’s suggestion)
func Run() error {
return route.Run()
}
foundation/application.go
Outdated
|
|
||
| path = append([]string{support.RelativePath, defaultPath}, path...) | ||
| return r.absPath(path...) | ||
| for _, module := range modules { |
There was a problem hiding this comment.
Can module → runner be mapped in a map instead of using a switch?
There was a problem hiding this comment.
Users can customize the modules, so it's harder to build the map.
There was a problem hiding this comment.
No, I’m just talking about the switch case instead of using a switch, we can map each module to its corresponding run method, then resolve the run method based on the modules the user has configured.
There was a problem hiding this comment.
Got it, will think about it with @almas-x 's optimization.
foundation/application.go
Outdated
| *Container | ||
|
|
||
| ctx context.Context | ||
| cancel context.CancelFunc |
There was a problem hiding this comment.
looks like they(quit and cancel) are not required to be struct level params?
|
Amazing job👍 This change makes |
|
@almas-x Good point to provide a way to support the third package. I'll think about it. |
|
Added a new |
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Most of these runners in type ServiceProviderWithRunner interface {
ServiceProvider
Runner() Runner
}After loading all |
|
@almas-x Good idea, only have one concern: how to stop a runner in this way? According to the current logic, users can customize the runners: |
I think the graceful shutdown logic could be moved into Application.Run. Start each runner one by one, and when a quit signal is received, gracefully shut down each runner in order. This centralizes lifecycle management and makes the codebase more maintainable. What are your thoughts on this? @hwbrzzl |
|
@almas-x Do you mean:
|
@hwbrzzl I feel runner is more closely related to a facade rather than the service provider itself, since a single provider can expose multiple facades. So instead of making the provider implement runner logic, maybe the provider can just tell us which of its facades implement Something like how we already define Relationship, maybe we can add fields for that: func (r *ServiceProvider) Relationship() contractsbinding.Relationship {
bindings := []string{
contractsbinding.Orm,
contractsbinding.DB,
contractsbinding.Schema,
contractsbinding.Seeder,
}
return contractsbinding.Relationship{
Bindings: bindings,
Dependencies: binding.Dependencies(bindings...),
ProvideFor: []string{},
Runner: []string{}, // facades implementing Runner
Shutdown: []string{}, // facades implementing Shutdown
}
}This way, the framework can look up which facades actually implement those interfaces and handle start/stop automatically. |
Yep 😉 |
That’s a great point, I forgot that a single type ServiceProviderWithRunners interface {
ServiceProvider
Runners() []Runner
} |
@hwbrzzl Hmm, I agree, it feels odd to put runners in Relationship(). As @almas-x suggested, we can add type Runner interface {
ShouldRun() bool
Run() error
Shutdown() error
}
type ServiceProvider interface {
Runners() []Runner | []Bindings
}Example: type Example struct {
host string
}
func (r *Example ) ShouldRun() bool {
return r.host != "" // or any other condition derived from config facade
}
func (r *Example) Run() error {
return nil
}
func (r *Example ) Shutdown() error {
return nil
} |
|
Updated, PTAL. @goravel/core-developers |
krishankumar01
left a comment
There was a problem hiding this comment.
Looks good 👍, just a couple of questions.
| Boot(app Application) | ||
| } | ||
|
|
||
| type ServiceProviderWithRunners interface { |
There was a problem hiding this comment.
Shouldn’t these interfaces just extend the original ServiceProvider interface?
type ServiceProviderWithRunners interface {
ServiceProvider
// Runners returns the service provider's runners.
Runners(app Application) []Runner
}
type ServiceProviderWithRelations interface {
ServiceProvider
// Relationship returns the service provider's relationship.
Relationship() binding.Relationship
}| setEnv() | ||
| setRootPath() | ||
|
|
||
| ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) |
There was a problem hiding this comment.
SIGTERM is Unix-specific, right? Can we confirm if this will work on Windows?
There was a problem hiding this comment.
Yes, Windows doesn't support SIGTERM, but there is no error on Windows. The code is fine.
foundation/application.go
Outdated
| go func() { | ||
| <-r.ctx.Done() | ||
|
|
||
| for _, runner := range allRunners { |
There was a problem hiding this comment.
Will graceful shutdown work here (like graceful shutdown of HTTP server)? Shouldn't we wait for all runner to actually Run using WaitGroup or similar?
There was a problem hiding this comment.
Good question, and if one runner fails to shut down, other runners will be blocked. I think it's better to split them to multiple goroutines. cc @almas-x
There was a problem hiding this comment.
Good question, and if one runner fails to shut down, other runners will be blocked. I think it's better to split them to multiple goroutines. cc @almas-x
That makes sense, splitting them into multiple goroutines since shutting down each runner usually won’t affect the others.
|
Addressed comments, PTAL @goravel/core-developers |






📑 Description
Closes https://github.com/goravel/goravel/issues/
Related goravel/goravel#785
Runfunction to runRoute,Grpc,Queue, andSchedule.Ctxfunction to listen to the quit signal.✅ Checks