Skip to content
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

feat: hot reload #3392

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 50 additions & 12 deletions backend/controller/scaling/localscaling/local_scaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,31 @@ type localScaling struct {
ideSupport optional.Option[localdebug.IDEIntegration]
registryConfig artefacts.RegistryConfig
enableOtel bool

devModeEndpointsUpdates <-chan scaling.DevModeEndpoints
devModeEndpoints map[string]*devModeRunner
}

type devModeRunner struct {
uri url.URL
deploymentKey string
}

func (l *localScaling) Start(ctx context.Context, endpoint url.URL, leaser leases.Leaser) error {
go func() {
for {
select {
case <-ctx.Done():
return
case devEndpoints := <-l.devModeEndpointsUpdates:
l.lock.Lock()
l.devModeEndpoints[devEndpoints.Module] = &devModeRunner{
uri: devEndpoints.Endpoint,
}
l.lock.Unlock()
}
}
}()
scaling.BeginGrpcScaling(ctx, endpoint, leaser, l.handleSchemaChange)
return nil
}
Expand Down Expand Up @@ -84,22 +106,24 @@ type runnerInfo struct {
port string
}

func NewLocalScaling(portAllocator *bind.BindAllocator, controllerAddresses []*url.URL, configPath string, enableIDEIntegration bool, registryConfig artefacts.RegistryConfig, enableOtel bool) (scaling.RunnerScaling, error) {
func NewLocalScaling(portAllocator *bind.BindAllocator, controllerAddresses []*url.URL, configPath string, enableIDEIntegration bool, registryConfig artefacts.RegistryConfig, enableOtel bool, devModeEndpoints <-chan scaling.DevModeEndpoints) (scaling.RunnerScaling, error) {

cacheDir, err := os.UserCacheDir()
if err != nil {
return nil, err
}
local := localScaling{
lock: sync.Mutex{},
cacheDir: cacheDir,
runners: map[string]map[string]*deploymentInfo{},
portAllocator: portAllocator,
controllerAddresses: controllerAddresses,
prevRunnerSuffix: -1,
debugPorts: map[string]*localdebug.DebugInfo{},
registryConfig: registryConfig,
enableOtel: enableOtel,
lock: sync.Mutex{},
cacheDir: cacheDir,
runners: map[string]map[string]*deploymentInfo{},
portAllocator: portAllocator,
controllerAddresses: controllerAddresses,
prevRunnerSuffix: -1,
debugPorts: map[string]*localdebug.DebugInfo{},
registryConfig: registryConfig,
enableOtel: enableOtel,
devModeEndpointsUpdates: devModeEndpoints,
devModeEndpoints: map[string]*devModeRunner{},
}
if enableIDEIntegration && configPath != "" {
local.ideSupport = optional.Ptr(localdebug.NewIDEIntegration(configPath))
Expand Down Expand Up @@ -171,6 +195,17 @@ func (l *localScaling) startRunner(ctx context.Context, deploymentKey string, in
return nil
default:
}

devEndpoint := l.devModeEndpoints[info.module]
devURI := optional.None[url.URL]()
if devEndpoint != nil {
devURI = optional.Some(devEndpoint.uri)
if devEndpoint.deploymentKey == deploymentKey {
// Already running, don't start another
return nil
}
devEndpoint.deploymentKey = deploymentKey
}
controllerEndpoint := l.controllerAddresses[len(l.runners)%len(l.controllerAddresses)]
logger := log.FromContext(ctx)

Expand Down Expand Up @@ -207,6 +242,7 @@ func (l *localScaling) startRunner(ctx context.Context, deploymentKey string, in
ObservabilityConfig: observability.Config{
ExportOTEL: observability.ExportOTELFlag(l.enableOtel),
},
DevEndpoint: devURI,
}

simpleName := fmt.Sprintf("runner%d", keySuffix)
Expand All @@ -229,10 +265,12 @@ func (l *localScaling) startRunner(ctx context.Context, deploymentKey string, in
err := runner.Start(runnerCtx, config)
l.lock.Lock()
defer l.lock.Unlock()
if devEndpoint != nil {
devEndpoint.deploymentKey = ""
}
// Don't count context.Canceled as an a restart error
if err != nil && !errors.Is(err, context.Canceled) {
logger.Errorf(err, "Runner failed: %s", err)
} else {
// Don't count context.Canceled as an a restart error
info.exits++
}
if info.exits >= maxExits {
Expand Down
5 changes: 5 additions & 0 deletions backend/controller/scaling/scaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ func runGrpcScaling(ctx context.Context, url url.URL, handler func(ctx context.C
rpc.RetryStreamingServerStream(ctx, "local-scaling", backoff.Backoff{Max: time.Second}, &ftlv1.PullSchemaRequest{}, client.PullSchema, handler, rpc.AlwaysRetry())
logger.Debugf("Stopped Runner Scaling")
}

type DevModeEndpoints struct {
Module string
Endpoint url.URL
}
415 changes: 233 additions & 182 deletions backend/protos/xyz/block/ftl/v1/language/language.pb.go

Large diffs are not rendered by default.

29 changes: 20 additions & 9 deletions backend/protos/xyz/block/ftl/v1/language/language.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ message ModuleConfig {
string deploy_dir = 4;
// Build is the command to build the module.
optional string build = 5;
// DevModeBuild is the command to build the module in dev mode.
optional string devModeBuild = 6;
// Build lock path to prevent concurrent builds
string build_lock = 6;
string build_lock = 7;
// The directory to generate protobuf schema files into. These can be picked up by language specific build tools
optional string generated_schema_dir = 7;
optional string generated_schema_dir = 8;
// Patterns to watch for file changes
repeated string watch = 8;
repeated string watch = 9;

// LanguageConfig contains any metadata specific to a specific language.
// These are stored in the ftl.toml file under the same key as the language (eg: "go", "java")
google.protobuf.Struct language_config = 9;
google.protobuf.Struct language_config = 10;
}

// ProjectConfig contains the configuration for a project, found in the ftl-project.toml file.
Expand Down Expand Up @@ -94,18 +96,21 @@ message ModuleConfigDefaultsResponse {
// Default build command
optional string build = 2;

// Dev mode build command, if different from the regular build command
optional string devModeBuild = 3;

// Build lock path to prevent concurrent builds
optional string build_lock = 3;
optional string build_lock = 4;

// Default relative path to the directory containing generated schema files
optional string generated_schema_dir = 4;
optional string generated_schema_dir = 5;

// Default patterns to watch for file changes, relative to the module directory
repeated string watch = 5;
repeated string watch = 6;

// Default language specific configuration.
// These defaults are filled in by looking at each root key only. If the key is not present, the default is used.
google.protobuf.Struct language_config = 6;
google.protobuf.Struct language_config = 7;
}

message DependenciesRequest {
Expand Down Expand Up @@ -208,11 +213,17 @@ message BuildSuccess {
schema.Module module = 3;
// Paths for files/directories to be deployed
repeated string deploy = 4;
// Name of the docker image to use for the runner
string docker_image = 5;

// Errors contains any errors that occurred during the build
// No errors can have a level of ERROR, instead a BuildFailure should be sent
// Instead this is useful for INFO and WARN level errors.
ErrorList errors = 5;
ErrorList errors = 6;

// Dev mode endpoint URI. If this is set then rather than trying to deploy the module, FTL will start a runner that
// connects to this endpoint.
optional string dev_endpoint = 7;
}

// BuildFailure should be sent when a build fails.
Expand Down
6 changes: 5 additions & 1 deletion backend/protos/xyz/block/ftl/v1/language/mixins.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"

"github.com/alecthomas/types/optional"
structpb "google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/structpb"

"github.com/TBD54566975/ftl/internal/builderrors"
"github.com/TBD54566975/ftl/internal/moduleconfig"
Expand Down Expand Up @@ -100,6 +100,9 @@ func ModuleConfigToProto(config moduleconfig.AbsModuleConfig) (*ModuleConfig, er
if config.Build != "" {
proto.Build = &config.Build
}
if config.DevModeBuild != "" {
proto.DevModeBuild = &config.DevModeBuild
}
if config.GeneratedSchemaDir != "" {
proto.GeneratedSchemaDir = &config.GeneratedSchemaDir
}
Expand All @@ -122,6 +125,7 @@ func ModuleConfigFromProto(proto *ModuleConfig) moduleconfig.AbsModuleConfig {
Watch: proto.Watch,
Language: proto.Language,
Build: proto.GetBuild(),
DevModeBuild: proto.GetDevModeBuild(),
BuildLock: proto.BuildLock,
GeneratedSchemaDir: proto.GetGeneratedSchemaDir(),
}
Expand Down
Loading
Loading