예제 #1
0
        /// <summary>
        /// The core queue processing method
        /// </summary>
        /// <param name="state"></param>
        private void ProcessQueue()
        {
            CancellationToken token = this.processQueueCancelToken.Token;

            WaitHandle[] waitHandles = new WaitHandle[2]
            {
                this.itemQueuedEvent,
                token.WaitHandle
            };

            while (true)
            {
                // wait for with an item to be queued or the a cancellation request
                WaitHandle.WaitAny(waitHandles);
                if (token.IsCancellationRequested)
                {
                    break;
                }

                try
                {
                    // dispatch all pending queue items
                    while (this.HasPendingQueueItems)
                    {
                        QueueItem queueItem = GetNextQueueItem();
                        if (queueItem == null)
                        {
                            continue;
                        }

                        IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
                        if (bindingContext == null)
                        {
                            queueItem.ItemProcessed.Set();
                            continue;
                        }

                        bool lockTaken = false;
                        try
                        {
                            // prefer the queue item binding item, otherwise use the context default timeout
                            int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;

                            // handle the case a previous binding operation is still running
                            if (!bindingContext.BindingLock.WaitOne(queueItem.WaitForLockTimeout ?? 0))
                            {
                                queueItem.Result = queueItem.TimeoutOperation != null
                                    ? queueItem.TimeoutOperation(bindingContext)
                                    : null;

                                continue;
                            }

                            bindingContext.BindingLock.Reset();

                            lockTaken = true;

                            // execute the binding operation
                            object result = null;
                            CancellationTokenSource cancelToken = new CancellationTokenSource();
                            var bindTask = Task.Run(() =>
                            {
                                result = queueItem.BindOperation(
                                    bindingContext,
                                    cancelToken.Token);
                            });

                            // check if the binding tasks completed within the binding timeout
                            if (bindTask.Wait(bindTimeout))
                            {
                                queueItem.Result = result;
                            }
                            else
                            {
                                cancelToken.Cancel();

                                // if the task didn't complete then call the timeout callback
                                if (queueItem.TimeoutOperation != null)
                                {
                                    queueItem.Result = queueItem.TimeoutOperation(bindingContext);
                                }

                                lockTaken = false;

                                bindTask.ContinueWith((a) => bindingContext.BindingLock.Set());
                            }
                        }
                        catch (Exception ex)
                        {
                            // catch and log any exceptions raised in the binding calls
                            // set item processed to avoid deadlocks
                            Logger.Write(LogLevel.Error, "Binding queue threw exception " + ex.ToString());
                        }
                        finally
                        {
                            if (lockTaken)
                            {
                                bindingContext.BindingLock.Set();
                            }

                            queueItem.ItemProcessed.Set();
                        }

                        // if a queue processing cancellation was requested then exit the loop
                        if (token.IsCancellationRequested)
                        {
                            break;
                        }
                    }
                }
                finally
                {
                    lock (this.bindingQueueLock)
                    {
                        // verify the binding queue is still empty
                        if (this.bindingQueue.Count == 0)
                        {
                            // reset the item queued event since we've processed all the pending items
                            this.itemQueuedEvent.Reset();
                        }
                    }
                }
            }
        }
예제 #2
0
        /// <summary>
        /// Return the completion item list for the current text position.
        /// This method does not await cache builds since it expects to return quickly
        /// </summary>
        /// <param name="textDocumentPosition"></param>
        public CompletionItem[] GetCompletionItems(
            TextDocumentPosition textDocumentPosition,
            ScriptFile scriptFile,
            ConnectionInfo connInfo)
        {
            // initialize some state to parse and bind the current script file
            this.currentCompletionParseInfo = null;
            CompletionItem[] resultCompletionItems = null;
            string           filePath = textDocumentPosition.TextDocument.Uri;
            int startLine             = textDocumentPosition.Position.Line;
            int parserLine            = textDocumentPosition.Position.Line + 1;
            int startColumn           = TextUtilities.PositionOfPrevDelimeter(
                scriptFile.Contents,
                textDocumentPosition.Position.Line,
                textDocumentPosition.Position.Character);
            int endColumn = TextUtilities.PositionOfNextDelimeter(
                scriptFile.Contents,
                textDocumentPosition.Position.Line,
                textDocumentPosition.Position.Character);
            int  parserColumn            = textDocumentPosition.Position.Character + 1;
            bool useLowerCaseSuggestions = this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value;

            // get the current script parse info object
            ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);

            if (scriptParseInfo == null)
            {
                return(AutoCompleteHelper.GetDefaultCompletionItems(
                           startLine,
                           startColumn,
                           endColumn,
                           useLowerCaseSuggestions));
            }

            // reparse and bind the SQL statement if needed
            if (RequiresReparse(scriptParseInfo, scriptFile))
            {
                ParseAndBind(scriptFile, connInfo);
            }

            // if the parse failed then return the default list
            if (scriptParseInfo.ParseResult == null)
            {
                return(AutoCompleteHelper.GetDefaultCompletionItems(
                           startLine,
                           startColumn,
                           endColumn,
                           useLowerCaseSuggestions));
            }

            // need to adjust line & column for base-1 parser indices
            Token  token     = GetToken(scriptParseInfo, parserLine, parserColumn);
            string tokenText = token != null ? token.Text : null;

            // check if the file is connected and the file lock is available
            if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
            {
                try
                {
                    // queue the completion task with the binding queue
                    QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
                        key: scriptParseInfo.ConnectionKey,
                        bindingTimeout: LanguageService.BindingTimeout,
                        bindOperation: (bindingContext, cancelToken) =>
                    {
                        // get the completion list from SQL Parser
                        scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions(
                            scriptParseInfo.ParseResult,
                            parserLine,
                            parserColumn,
                            bindingContext.MetadataDisplayInfoProvider);

                        // cache the current script parse info object to resolve completions later
                        this.currentCompletionParseInfo = scriptParseInfo;

                        // convert the suggestion list to the VS Code format
                        return(AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
                                   scriptParseInfo.CurrentSuggestions,
                                   startLine,
                                   startColumn,
                                   endColumn));
                    },
                        timeoutOperation: (bindingContext) =>
                    {
                        // return the default list if the connected bind fails
                        return(AutoCompleteHelper.GetDefaultCompletionItems(
                                   startLine,
                                   startColumn,
                                   endColumn,
                                   useLowerCaseSuggestions,
                                   tokenText));
                    });

                    // wait for the queue item
                    queueItem.ItemProcessed.WaitOne();

                    var completionItems = queueItem.GetResultAsT <CompletionItem[]>();
                    if (completionItems != null && completionItems.Length > 0)
                    {
                        resultCompletionItems = completionItems;
                    }
                    else if (!ShouldShowCompletionList(token))
                    {
                        resultCompletionItems = AutoCompleteHelper.EmptyCompletionList;
                    }
                }
                finally
                {
                    Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
                }
            }

            // if there are no completions then provide the default list
            if (resultCompletionItems == null)
            {
                resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(
                    startLine,
                    startColumn,
                    endColumn,
                    useLowerCaseSuggestions,
                    tokenText);
            }

            return(resultCompletionItems);
        }
        /// <summary>
        /// Preinitialize the parser and binder with common metadata.
        /// This should front load the long binding wait to the time the
        /// connection is established.  Once this is completed other binding
        /// requests should be faster.
        /// </summary>
        /// <param name="info"></param>
        /// <param name="scriptInfo"></param>
        internal static void PrepopulateCommonMetadata(
            ConnectionInfo info,
            ScriptParseInfo scriptInfo,
            ConnectedBindingQueue bindingQueue)
        {
            if (scriptInfo.IsConnected)
            {
                var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri);
                LanguageService.Instance.ParseAndBind(scriptFile, info);

                if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
                {
                    try
                    {
                        QueueItem queueItem = bindingQueue.QueueBindingOperation(
                            key: scriptInfo.ConnectionKey,
                            bindingTimeout: AutoCompleteHelper.PrepopulateBindTimeout,
                            waitForLockTimeout: AutoCompleteHelper.PrepopulateBindTimeout,
                            bindOperation: (bindingContext, cancelToken) =>
                        {
                            // parse a simple statement that returns common metadata
                            ParseResult parseResult = Parser.Parse(
                                "select ",
                                bindingContext.ParseOptions);

                            List <ParseResult> parseResults = new List <ParseResult>();
                            parseResults.Add(parseResult);
                            bindingContext.Binder.Bind(
                                parseResults,
                                info.ConnectionDetails.DatabaseName,
                                BindMode.Batch);

                            // get the completion list from SQL Parser
                            var suggestions = Resolver.FindCompletions(
                                parseResult, 1, 8,
                                bindingContext.MetadataDisplayInfoProvider);

                            // this forces lazy evaluation of the suggestion metadata
                            AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);

                            parseResult = Parser.Parse(
                                "exec ",
                                bindingContext.ParseOptions);

                            parseResults = new List <ParseResult>();
                            parseResults.Add(parseResult);
                            bindingContext.Binder.Bind(
                                parseResults,
                                info.ConnectionDetails.DatabaseName,
                                BindMode.Batch);

                            // get the completion list from SQL Parser
                            suggestions = Resolver.FindCompletions(
                                parseResult, 1, 6,
                                bindingContext.MetadataDisplayInfoProvider);

                            // this forces lazy evaluation of the suggestion metadata
                            AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
                            return(null);
                        });

                        queueItem.ItemProcessed.WaitOne();
                    }
                    catch
                    {
                    }
                    finally
                    {
                        Monitor.Exit(scriptInfo.BuildingMetadataLock);
                    }
                }
            }
        }
예제 #4
0
        /// <summary>
        /// Parses the SQL text and binds it to the SMO metadata provider if connected
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="sqlText"></param>
        /// <returns>The ParseResult instance returned from SQL Parser</returns>
        public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
        {
            // get or create the current parse info object
            ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true);

            if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
            {
                try
                {
                    if (connInfo == null || !parseInfo.IsConnected)
                    {
                        // parse current SQL file contents to retrieve a list of errors
                        ParseResult parseResult = Parser.IncrementalParse(
                            scriptFile.Contents,
                            parseInfo.ParseResult,
                            this.DefaultParseOptions);

                        parseInfo.ParseResult = parseResult;
                    }
                    else
                    {
                        QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
                            key: parseInfo.ConnectionKey,
                            bindingTimeout: LanguageService.BindingTimeout,
                            bindOperation: (bindingContext, cancelToken) =>
                        {
                            try
                            {
                                ParseResult parseResult = Parser.IncrementalParse(
                                    scriptFile.Contents,
                                    parseInfo.ParseResult,
                                    bindingContext.ParseOptions);

                                parseInfo.ParseResult = parseResult;

                                List <ParseResult> parseResults = new List <ParseResult>();
                                parseResults.Add(parseResult);
                                bindingContext.Binder.Bind(
                                    parseResults,
                                    connInfo.ConnectionDetails.DatabaseName,
                                    BindMode.Batch);
                            }
                            catch (ConnectionException)
                            {
                                Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
                            }
                            catch (SqlParserInternalBinderError)
                            {
                                Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
                            }
                            catch (Exception ex)
                            {
                                Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString());
                            }

                            return(null);
                        });

                        queueItem.ItemProcessed.WaitOne();
                    }
                }
                catch (Exception ex)
                {
                    // reset the parse result to do a full parse next time
                    parseInfo.ParseResult = null;
                    Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString());
                }
                finally
                {
                    Monitor.Exit(parseInfo.BuildingMetadataLock);
                }
            }
            else
            {
                Logger.Write(LogLevel.Warning, "Binding metadata lock timeout in ParseAndBind");
            }

            return(parseInfo.ParseResult);
        }
예제 #5
0
        /// <summary>
        /// The core queue processing method
        /// </summary>
        /// <param name="state"></param>
        private void ProcessQueue()
        {
            CancellationToken token = this.processQueueCancelToken.Token;

            WaitHandle[] waitHandles = new WaitHandle[2]
            {
                this.itemQueuedEvent,
                token.WaitHandle
            };

            while (true)
            {
                // wait for with an item to be queued or the a cancellation request
                WaitHandle.WaitAny(waitHandles);
                if (token.IsCancellationRequested)
                {
                    break;
                }

                try
                {
                    // dispatch all pending queue items
                    while (this.HasPendingQueueItems)
                    {
                        QueueItem queueItem = GetNextQueueItem();
                        if (queueItem == null)
                        {
                            continue;
                        }

                        IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
                        if (bindingContext == null)
                        {
                            queueItem.ItemProcessed.Set();
                            continue;
                        }

                        var bindingContextTask = this.BindingContextTasks[bindingContext];

                        // Run in the binding context task in case this task has to wait for a previous binding operation
                        this.BindingContextTasks[bindingContext] = bindingContextTask.ContinueWith((task) =>
                        {
                            bool lockTaken = false;
                            try
                            {
                                // prefer the queue item binding item, otherwise use the context default timeout
                                int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;

                                // handle the case a previous binding operation is still running
                                if (!bindingContext.BindingLock.WaitOne(queueItem.WaitForLockTimeout ?? 0))
                                {
                                    try
                                    {
                                        Logger.Write(TraceEventType.Warning, "Binding queue operation timed out waiting for previous operation to finish");
                                        queueItem.Result = queueItem.TimeoutOperation != null
                                            ? queueItem.TimeoutOperation(bindingContext)
                                            : null;
                                    }
                                    catch (Exception ex)
                                    {
                                        Logger.Write(TraceEventType.Error, "Exception running binding queue lock timeout handler: " + ex.ToString());
                                    }
                                    finally
                                    {
                                        queueItem.ItemProcessed.Set();
                                    }

                                    return;
                                }

                                bindingContext.BindingLock.Reset();

                                lockTaken = true;

                                // execute the binding operation
                                object result = null;
                                CancellationTokenSource cancelToken = new CancellationTokenSource();

                                // run the operation in a separate thread
                                var bindTask = Task.Run(() =>
                                {
                                    try
                                    {
                                        result = queueItem.BindOperation(
                                            bindingContext,
                                            cancelToken.Token);
                                    }
                                    catch (Exception ex)
                                    {
                                        Logger.Write(TraceEventType.Error, "Unexpected exception on the binding queue: " + ex.ToString());
                                        if (queueItem.ErrorHandler != null)
                                        {
                                            try
                                            {
                                                result = queueItem.ErrorHandler(ex);
                                            }
                                            catch (Exception ex2)
                                            {
                                                Logger.Write(TraceEventType.Error, "Unexpected exception in binding queue error handler: " + ex2.ToString());
                                            }
                                        }

                                        if (IsExceptionOfType(ex, typeof(SqlException)) || IsExceptionOfType(ex, typeof(SocketException)))
                                        {
                                            if (this.OnUnhandledException != null)
                                            {
                                                this.OnUnhandledException(queueItem.Key, ex);
                                            }

                                            RemoveBindingContext(queueItem.Key);
                                        }
                                    }
                                });

                                Task.Run(() =>
                                {
                                    try
                                    {
                                        // check if the binding tasks completed within the binding timeout
                                        if (bindTask.Wait(bindTimeout))
                                        {
                                            queueItem.Result = result;
                                        }
                                        else
                                        {
                                            cancelToken.Cancel();

                                            // if the task didn't complete then call the timeout callback
                                            if (queueItem.TimeoutOperation != null)
                                            {
                                                queueItem.Result = queueItem.TimeoutOperation(bindingContext);
                                            }

                                            bindTask.ContinueWithOnFaulted(t => Logger.Write(TraceEventType.Error, "Binding queue threw exception " + t.Exception.ToString()));

                                            // Give the task a chance to complete before moving on to the next operation
                                            bindTask.Wait();
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        Logger.Write(TraceEventType.Error, "Binding queue task completion threw exception " + ex.ToString());
                                    }
                                    finally
                                    {
                                        // set item processed to avoid deadlocks
                                        if (lockTaken)
                                        {
                                            bindingContext.BindingLock.Set();
                                        }
                                        queueItem.ItemProcessed.Set();
                                    }
                                });
                            }
                            catch (Exception ex)
                            {
                                // catch and log any exceptions raised in the binding calls
                                // set item processed to avoid deadlocks
                                Logger.Write(TraceEventType.Error, "Binding queue threw exception " + ex.ToString());
                                // set item processed to avoid deadlocks
                                if (lockTaken)
                                {
                                    bindingContext.BindingLock.Set();
                                }
                                queueItem.ItemProcessed.Set();
                            }
                        }, TaskContinuationOptions.None);

                        // if a queue processing cancellation was requested then exit the loop
                        if (token.IsCancellationRequested)
                        {
                            break;
                        }
                    }
                }
                finally
                {
                    lock (this.bindingQueueLock)
                    {
                        // verify the binding queue is still empty
                        if (this.bindingQueue.Count == 0)
                        {
                            // reset the item queued event since we've processed all the pending items
                            this.itemQueuedEvent.Reset();
                        }
                    }
                }
            }
        }