Skip to content

Commit

Permalink
Merge pull request #714 from hashicorp/f-mod-ops-sync
Browse files Browse the repository at this point in the history
refactor: decouple diagnostics from handlers into a hook
  • Loading branch information
radeksimko authored Nov 17, 2021
2 parents 877dcee + 7611112 commit 41d0388
Show file tree
Hide file tree
Showing 16 changed files with 110 additions and 121 deletions.
19 changes: 11 additions & 8 deletions internal/langserver/diagnostics/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"path/filepath"
"sync"

"github.com/creachadair/jrpc2"
"github.com/hashicorp/hcl/v2"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
Expand All @@ -21,20 +20,24 @@ type diagContext struct {

type DiagnosticSource string

type ClientNotifier interface {
Notify(ctx context.Context, method string, params interface{}) error
}

// Notifier is a type responsible for queueing HCL diagnostics to be converted
// and sent to the client
type Notifier struct {
logger *log.Logger
sessCtx context.Context
diags chan diagContext
clientNotifier ClientNotifier
closeDiagsOnce sync.Once
}

func NewNotifier(sessCtx context.Context, logger *log.Logger) *Notifier {
func NewNotifier(clientNotifier ClientNotifier, logger *log.Logger) *Notifier {
n := &Notifier{
logger: logger,
sessCtx: sessCtx,
diags: make(chan diagContext, 50),
logger: logger,
diags: make(chan diagContext, 50),
clientNotifier: clientNotifier,
}
go n.notify()
return n
Expand All @@ -44,7 +47,7 @@ func NewNotifier(sessCtx context.Context, logger *log.Logger) *Notifier {
// A dir path is passed which is joined with the filename keys of the map, to form a file URI.
func (n *Notifier) PublishHCLDiags(ctx context.Context, dirPath string, diags Diagnostics) {
select {
case <-n.sessCtx.Done():
case <-ctx.Done():
n.closeDiagsOnce.Do(func() {
close(n.diags)
})
Expand All @@ -68,7 +71,7 @@ func (n *Notifier) PublishHCLDiags(ctx context.Context, dirPath string, diags Di

func (n *Notifier) notify() {
for d := range n.diags {
if err := jrpc2.ServerFromContext(d.ctx).Notify(d.ctx, "textDocument/publishDiagnostics", lsp.PublishDiagnosticsParams{
if err := n.clientNotifier.Notify(d.ctx, "textDocument/publishDiagnostics", lsp.PublishDiagnosticsParams{
URI: d.uri,
Diagnostics: d.diags,
}); err != nil {
Expand Down
24 changes: 14 additions & 10 deletions internal/langserver/diagnostics/diagnostics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import (
var discardLogger = log.New(ioutil.Discard, "", 0)

func TestDiags_Closes(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
n := NewNotifier(ctx, discardLogger)

cancel()
n := NewNotifier(noopNotifier{}, discardLogger)

diags := NewDiagnostics()
diags.Append("test", map[string]hcl.Diagnostics{
Expand All @@ -27,7 +24,9 @@ func TestDiags_Closes(t *testing.T) {
},
})

n.PublishHCLDiags(context.Background(), t.TempDir(), diags)
ctx, cancel := context.WithCancel(context.Background())
cancel()
n.PublishHCLDiags(ctx, t.TempDir(), diags)

if _, open := <-n.diags; open {
t.Fatal("channel should be closed")
Expand All @@ -41,10 +40,7 @@ func TestPublish_DoesNotSendAfterClose(t *testing.T) {
}
}()

ctx, cancel := context.WithCancel(context.Background())
n := NewNotifier(ctx, discardLogger)

cancel()
n := NewNotifier(noopNotifier{}, discardLogger)

diags := NewDiagnostics()
diags.Append("test", map[string]hcl.Diagnostics{
Expand All @@ -55,7 +51,9 @@ func TestPublish_DoesNotSendAfterClose(t *testing.T) {
},
})

n.PublishHCLDiags(context.Background(), t.TempDir(), diags)
ctx, cancel := context.WithCancel(context.Background())
cancel()
n.PublishHCLDiags(ctx, t.TempDir(), diags)
}

func TestDiagnostics_Append(t *testing.T) {
Expand Down Expand Up @@ -133,3 +131,9 @@ func TestDiagnostics_Append(t *testing.T) {
t.Fatalf("diagnostics mismatch: %s", diff)
}
}

type noopNotifier struct{}

func (noopNotifier) Notify(ctx context.Context, method string, params interface{}) error {
return nil
}
33 changes: 5 additions & 28 deletions internal/langserver/handlers/did_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import (
"fmt"

lsctx "github.com/hashicorp/terraform-ls/internal/context"
"github.com/hashicorp/terraform-ls/internal/langserver/diagnostics"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
"github.com/hashicorp/terraform-ls/internal/terraform/ast"
op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation"
)

Expand Down Expand Up @@ -61,47 +59,26 @@ func TextDocumentDidChange(ctx context.Context, params lsp.DidChangeTextDocument
return err
}

err = modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeParseModuleConfiguration)
err = modMgr.EnqueueModuleOp(mod.Path, op.OpTypeParseModuleConfiguration, nil)
if err != nil {
return err
}
err = modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeParseVariables)
err = modMgr.EnqueueModuleOp(mod.Path, op.OpTypeParseVariables, nil)
if err != nil {
return err
}
// TODO: parallelise the operations below in a workgroup
err = modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeLoadModuleMetadata)
err = modMgr.EnqueueModuleOp(mod.Path, op.OpTypeLoadModuleMetadata, nil)
if err != nil {
return err
}
err = modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeDecodeReferenceTargets)
err = modMgr.EnqueueModuleOp(mod.Path, op.OpTypeDecodeReferenceTargets, nil)
if err != nil {
return err
}
err = modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeDecodeReferenceOrigins)
err = modMgr.EnqueueModuleOp(mod.Path, op.OpTypeDecodeReferenceOrigins, nil)
if err != nil {
return err
}

notifier, err := lsctx.DiagnosticsNotifier(ctx)
if err != nil {
return err
}

// obtain fresh module state after the above operations finished
mod, err = modMgr.ModuleByPath(fh.Dir())
if err != nil {
return err
}

diags := diagnostics.NewDiagnostics()
diags.EmptyRootDiagnostic()
diags.Append("HCL", mod.ModuleDiagnostics.AsMap())
diags.Append("HCL", mod.VarsDiagnostics.AutoloadedOnly().AsMap())
if vf, ok := ast.NewVarsFilename(f.Filename()); ok && !vf.IsAutoloaded() {
diags.Append("HCL", mod.VarsDiagnostics.ForFile(vf).AsMap())
}
notifier.PublishHCLDiags(ctx, mod.Path, diags)

return nil
}
17 changes: 0 additions & 17 deletions internal/langserver/handlers/did_close.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ package handlers
import (
"context"

"github.com/hashicorp/hcl/v2"
lsctx "github.com/hashicorp/terraform-ls/internal/context"
"github.com/hashicorp/terraform-ls/internal/langserver/diagnostics"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
"github.com/hashicorp/terraform-ls/internal/terraform/ast"
)

func TextDocumentDidClose(ctx context.Context, params lsp.DidCloseTextDocumentParams) error {
Expand All @@ -23,19 +20,5 @@ func TextDocumentDidClose(ctx context.Context, params lsp.DidCloseTextDocumentPa
return err
}

if vf, ok := ast.NewVarsFilename(fh.Filename()); ok && !vf.IsAutoloaded() {
notifier, err := lsctx.DiagnosticsNotifier(ctx)
if err != nil {
return err
}

diags := diagnostics.NewDiagnostics()
diags.EmptyRootDiagnostic()
diags.Append("HCL", map[string]hcl.Diagnostics{
fh.Filename(): {},
})
notifier.PublishHCLDiags(ctx, fh.Dir(), diags)
}

return nil
}
27 changes: 5 additions & 22 deletions internal/langserver/handlers/did_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import (
"context"

lsctx "github.com/hashicorp/terraform-ls/internal/context"
"github.com/hashicorp/terraform-ls/internal/langserver/diagnostics"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
"github.com/hashicorp/terraform-ls/internal/terraform/ast"
"github.com/hashicorp/terraform-ls/internal/terraform/module"
op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation"
)
Expand Down Expand Up @@ -48,11 +46,11 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe
// We reparse because the file being opened may not match
// (originally parsed) content on the disk
// TODO: Do this only if we can verify the file differs?
modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeParseModuleConfiguration)
modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeParseVariables)
modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeLoadModuleMetadata)
modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeDecodeReferenceTargets)
modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeDecodeReferenceOrigins)
modMgr.EnqueueModuleOp(mod.Path, op.OpTypeParseModuleConfiguration, nil)
modMgr.EnqueueModuleOp(mod.Path, op.OpTypeParseVariables, nil)
modMgr.EnqueueModuleOp(mod.Path, op.OpTypeLoadModuleMetadata, nil)
modMgr.EnqueueModuleOp(mod.Path, op.OpTypeDecodeReferenceTargets, nil)
modMgr.EnqueueModuleOp(mod.Path, op.OpTypeDecodeReferenceOrigins, nil)

if mod.TerraformVersionState == op.OpStateUnknown {
modMgr.EnqueueModuleOp(mod.Path, op.OpTypeGetTerraformVersion, nil)
Expand All @@ -70,20 +68,5 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe
}
}

notifier, err := lsctx.DiagnosticsNotifier(ctx)
if err != nil {
return err
}

diags := diagnostics.NewDiagnostics()
diags.EmptyRootDiagnostic()
diags.Append("HCL", mod.ModuleDiagnostics.AsMap())
diags.Append("HCL", mod.VarsDiagnostics.AutoloadedOnly().AsMap())
if vf, ok := ast.NewVarsFilename(f.Filename()); ok && !vf.IsAutoloaded() {
diags.Append("HCL", mod.VarsDiagnostics.ForFile(vf).AsMap())
}

notifier.PublishHCLDiags(ctx, mod.Path, diags)

return nil
}
28 changes: 28 additions & 0 deletions internal/langserver/handlers/hooks_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"

"github.com/hashicorp/terraform-ls/internal/langserver/diagnostics"
"github.com/hashicorp/terraform-ls/internal/state"
"github.com/hashicorp/terraform-ls/internal/telemetry"
"github.com/hashicorp/terraform-schema/backend"
Expand Down Expand Up @@ -91,3 +92,30 @@ func sendModuleTelemetry(ctx context.Context, store *state.StateStore, telemetry
telemetrySender.SendEvent(ctx, "moduleData", properties)
}
}

func updateDiagnostics(ctx context.Context, notifier *diagnostics.Notifier) state.ModuleChangeHook {
return func(oldMod, newMod *state.Module) {
// TODO: check if diagnostics have actually changed
oldDiags, newDiags := 0, 0
if oldMod != nil {
oldDiags = len(oldMod.ModuleDiagnostics) + len(oldMod.VarsDiagnostics)
}
if newMod != nil {
newDiags = len(newMod.ModuleDiagnostics) + len(newMod.VarsDiagnostics)
}

diags := diagnostics.NewDiagnostics()
diags.EmptyRootDiagnostic()

defer notifier.PublishHCLDiags(ctx, newMod.Path, diags)

if oldDiags == 0 && newDiags == 0 {
return
}

if newMod != nil {
diags.Append("HCL", newMod.ModuleDiagnostics.AsMap())
diags.Append("HCL", newMod.VarsDiagnostics.AutoloadedOnly().AsMap())
}
}
}
4 changes: 3 additions & 1 deletion internal/langserver/handlers/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ func (svc *service) Initialize(ctx context.Context, params lsp.InitializeParams)

expClientCaps := lsp.ExperimentalClientCapabilities(clientCaps.Experimental)

svc.server = jrpc2.ServerFromContext(ctx)

if tv, ok := expClientCaps.TelemetryVersion(); ok {
svc.logger.Printf("enabling telemetry (version: %d)", tv)
err := svc.setupTelemetry(tv, jrpc2.ServerFromContext(ctx))
err := svc.setupTelemetry(tv, svc.server)
if err != nil {
svc.logger.Printf("failed to setup telemetry: %s", err)
}
Expand Down
14 changes: 7 additions & 7 deletions internal/langserver/handlers/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type service struct {
telemetry telemetry.Sender
decoder *decoder.Decoder
stateStore *state.StateStore
server session.Server
diagsNotifier *diagnostics.Notifier

additionalHandlers map[string]rpch.Func
}
Expand Down Expand Up @@ -100,8 +102,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
lh := LogHandler(svc.logger)
cc := &lsp.ClientCapabilities{}

notifier := diagnostics.NewNotifier(svc.sessCtx, svc.logger)

rootDir := ""
commandPrefix := ""
clientName := ""
Expand Down Expand Up @@ -140,7 +140,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
if err != nil {
return nil, err
}
ctx = lsctx.WithDiagnosticsNotifier(ctx, notifier)
ctx = lsctx.WithDocumentStorage(ctx, svc.fs)
ctx = lsctx.WithModuleManager(ctx, svc.modMgr)
return handle(ctx, req, TextDocumentDidChange)
Expand All @@ -150,7 +149,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
if err != nil {
return nil, err
}
ctx = lsctx.WithDiagnosticsNotifier(ctx, notifier)
ctx = lsctx.WithDocumentStorage(ctx, svc.fs)
ctx = lsctx.WithModuleManager(ctx, svc.modMgr)
ctx = lsctx.WithWatcher(ctx, svc.watcher)
Expand All @@ -161,7 +159,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
if err != nil {
return nil, err
}
ctx = lsctx.WithDiagnosticsNotifier(ctx, notifier)
ctx = lsctx.WithDocumentStorage(ctx, svc.fs)
return handle(ctx, req, TextDocumentDidClose)
},
Expand Down Expand Up @@ -287,7 +284,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
return nil, err
}

ctx = lsctx.WithDiagnosticsNotifier(ctx, notifier)
ctx = lsctx.WithDiagnosticsNotifier(ctx, svc.diagsNotifier)
ctx = lsctx.WithExperimentalFeatures(ctx, &expFeatures)
ctx = lsctx.WithModuleFinder(ctx, svc.modMgr)
ctx = exec.WithExecutorOpts(ctx, svc.tfExecOpts)
Expand Down Expand Up @@ -328,7 +325,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
ctx = lsctx.WithModuleWalker(ctx, svc.walker)
ctx = lsctx.WithWatcher(ctx, svc.watcher)
ctx = lsctx.WithRootDirectory(ctx, &rootDir)
ctx = lsctx.WithDiagnosticsNotifier(ctx, notifier)
ctx = lsctx.WithDiagnosticsNotifier(ctx, svc.diagsNotifier)
ctx = exec.WithExecutorOpts(ctx, svc.tfExecOpts)
ctx = exec.WithExecutorFactory(ctx, svc.tfExecFactory)

Expand Down Expand Up @@ -430,6 +427,8 @@ func (svc *service) configureSessionDependencies(ctx context.Context, cfgOpts *s
execOpts.Timeout = d
}

svc.diagsNotifier = diagnostics.NewNotifier(svc.server, svc.logger)

svc.tfExecOpts = execOpts

svc.sessCtx = exec.WithExecutorOpts(svc.sessCtx, execOpts)
Expand All @@ -445,6 +444,7 @@ func (svc *service) configureSessionDependencies(ctx context.Context, cfgOpts *s

svc.stateStore.SetLogger(svc.logger)
svc.stateStore.Modules.ChangeHooks = state.ModuleChangeHooks{
updateDiagnostics(svc.sessCtx, svc.diagsNotifier),
sendModuleTelemetry(svc.sessCtx, svc.stateStore, svc.telemetry),
}

Expand Down
9 changes: 9 additions & 0 deletions internal/langserver/session/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ type ClientNotifier interface {
}

type SessionFactory func(context.Context) Session

type ClientCaller interface {
Callback(ctx context.Context, method string, params interface{}) (*jrpc2.Response, error)
}

type Server interface {
ClientNotifier
ClientCaller
}
Loading

0 comments on commit 41d0388

Please sign in to comment.