Open ChuckJonas opened 7 years ago
There is no queuing or cancelling presently.
If you send second request before first had a chance to complete then both will be executed in parallel. Moreover, there is no way to say which request will complete first so your result file may not contain the result you expected.
With vim
this is a non issue because you can not send second code completion request before first one completes, vim
UI just does not allow you to do so.
At first glance I am not sure how code completion request cancelling could be achieved because all operations involved are synchronous and there is no waiting phase like for instance in case of deployment. The longest running operation is a project source scan (which is normally done once per session, on the first code completion call). Once it is completed first time, it is not done in synchronous mode again (while server is running). if we cancel full source scan for the first completion call then it would have to be done for the next one anyway, so cancelling in this situation does not buy any time.
If you have ideas please share.
I am not sure if there is any way to block while code completion is in progress in VSCode or maybe there is a way to show user that their request is in progress. If neither is possible then for VSCode plugin one might consider tracking whether previous completion has finished and do not send second completion request while first one is in progress, or simply not send second one at all.
I don't think I can block in vscode. I'm basically just implementing providers
and vscode handles all user interactions. Unfortunately, this means I don't even have great control over when a completion request is made.
For example, if I type:
Account acc = new Account();
vscode asks for completions basically every other character (can't figure out a pattern). I can specify Trigger characters
but that only seems to guaranteed that it will fire on that character (doesn't restrict when it fires).
How are completions "triggered" in VIM?
I was hoping there might be a timeout
parameter on the request that would kill off anything that doesn't respond in x seconds. Or maybe a command to kill all processing requests. I can seem how that would be difficult to implement though.
I might be able to solve this on my with the following:
1: Add a small delay to the request to catch rapid spamming from vscode and only listCompletions
on for the final request
2: Use my typescript implementation of your antlr
grammar to check if tooling-force can actually provide completions for the line/column before sending the request (not sure exactly what tokens I would need to look for)
Both of these have the potential to make the response time unacceptably slow.
Also:
I'm still trying to debug further exactly what conditions cause the service to get bogged down. Most the time it can handle multiple request fine, but once it gets bogged, it almost will never catch up to the fury of a heads down programmer.
One suspicion that might be contributing is how I'm providing credentials. If I include -sf.username
&& --sf.password
with every listCompletion
command, does the service send a login request EVERY time? Or does it check if it already has a token cached?
Sorry for the long response! As always, thanks for your help!
1: Add a small delay to the request to catch rapid spamming from vscode and only listCompletions on for the final request
Is it possible to record the fact if response to last call to code completion has been received ? If response has not been received then just do nothing on subsequent call to completion.
2: Use my typescript implementation of your antlr grammar to check if tooling-force can actually provide completions for the line/column before sending the request (not sure exactly what tokens I would need to look for)
Short of providing completion result yourself I can not see how this can be used. There is no way to see if completion can be provided until all options to provide this completion have been tried.
One suspicion that might be contributing is how I'm providing credentials.
As long as server has access to session.properties file in .vim-force.com directory (from --projectPath
) it will use cached session token first
How are completions "triggered" in VIM?
Here is an example of code completion call from vim
--action=listCompletions --tempFolderPath='/Users/username/temp/apex/gvim deployment'
--authConfigPath='/Users/username/Private/SFDC Credentials/oauth2/SForce (vim-force.com)'
--config='/Users/username/Private/SFDC Credentials/SForce (vim-force.com).properties'
--projectPath='/Users/username/eclipse.workspace/Sforce - SFDC Experiments/SForce (vim-force.com)'
--column=13 --line=13 --currentFilePath='/Users/username/eclipse.workspace/Sforce - SFDC Experiments/SForce (vim-force.com)/src/classes/CompletionTester.cls'
--currentFileContentPath='/var/folders/1l/lwgr141d1hdbcs8zsy6sh36m0000gn/T/vPQevxn/81CompletionTester.cls'
--responseFilePath='/Users/username/eclipse.workspace/Sforce - SFDC Experiments/SForce (vim-force.com)/.vim-force.com/response_listCompletions'
--pollWaitMillis=1000 --maxPollRequests=1000
In the above example client provides both OAuth2
credentials (--authConfigPath
) and login/pass (--config
), but server will always do something like this
--projectPath
/.vim-force.com--authConfigPath
(if one provided) or login/pass from --config
or command line--pollWaitMillis=1000 --maxPollRequests=1000
are not used in code completion call and simply ignored.
Client (vim
) on the other hand just waits for response to complete (editor area of the UI is frozen) and I am not aware of any graceful way to trigger second completion before first one had a chance to complete (with success or failure).
While vim
is waiting for completion result user can press CTRL-C which does not send any cancel calls, but rather abandons completion on vim side and and unfreezes editor UI.
Current completion is done in following stages
describe global
). Describe global
in synchronous mode is done only once and then cachedOpportunity.Account.name
then we then have to call describe SObject
for Opportunity and then describe SObject
for Account.Theoretically before invoking every step of the above list we could check if cancel
call is received (does VSCode actually send cancel completion
call ?), but current version of tooling-force.com.jar
does not support cancellation for any calls.
Is it possible to record the fact if response to last call to code completion has been received ? If response has not been received then just do nothing on subsequent call to completion.
Yes, but I guess the problem with that is that only the last completion request from vscode is really valid. For example in foo.method(bar.
if vscode request completion on both .
and I cancel the second one, the user will not receive completions.
Short of providing completion result yourself I can not see how this can be used. There is no way to see if completion can be provided until all options to provide this completion have been tried.
Correct me if I'm wrong, but I only know of 3 instances where tooling-force
actually provides completion:
1: On .
accessors
2: @
attributes
3: inside of SOQL/SOSL queries
It doesn't seem to provide type completions on variable declarations or constructors. But I'm probably not considering all the edge cases I would need to handle to implement the approach I mentioned above.
In the above example client provides both OAuth2 credentials (--authConfigPath) and login/pass (--config), but server will always do something like this check if we have session token in --projectPath/.vim-force.com if above does not have session token or it has expired -- use credentials from --authConfigPath (if one provided) or login/pass from --config or command line
Ok, I think the way I'm sending it it should be using the token after initial auth:
--action=listCompletions --projectPath='/Users/johndoe/Documents/project' --responseFilePath='/Users/johndoe/.vscode/extensions/chuckjonas.apex-autocomplete-0.2.13/bin/listCompletionsResult.json' --pollWaitMillis=1000 --maxPollRequests=1000 --currentFilePath='/Users/johndoe/Documents/project/src/classes/MyClass.cls' --currentFileContentPath='/Users/johndoe/.vscode/extensions/chuckjonas.apex-autocomplete-0.2.13/bin/listCompletionsTmp.cls' --line='36' --column='32' --sf.username='abc@example.com' --sf.password='password1' --sf.serverurl='https://test.salesforce.com'
I'm using sf.username
& sf.password
because the extension just takes advantage of vscode's workspace/user settings to configure authentication (and that way I don't have to copy the password around).
The session.properties
file does get created under the vim-force.com
folder so I assume it's properly using that for subsequent requests. I'd like to also allow for oAuth (another discussion for another time). The only other difference I see is --tempFolderPath
.
Theoretically before invoking every step of the above list we could check if cancel call is received (does VSCode actually send cancel completion call ?), but current version of tooling-force.com.jar does not support cancellation for any calls.
I think something like that would work with my implementation.
The vscode CompletionProvider does pass a CancellationToken in.
A cancellation token is passed to an asynchronous or long running operation to request cancellation, like cancelling a request for completion items because the user continued to type.
I would be able to cancel the request (--action=cancelCompletions
) using the token's onCancellationRequested
event.
However, I understand implementing that would likely be involved (I'm already very grateful for the time you spend on this project).
Correct me if I'm wrong, but I only know of 3 instances
Yes that is correct (except SOSL is not supported). I made wrong assumption about the meaning of your question.
The vscode CompletionProvider does pass a CancellationToken in.
This looks like something worth considering.
If user/client requested multiple completions too close to one another then there will be multiple threads running completion logic.
Do you know in which order VSCode sends completion and cancellation token ? Is it always
or can it be
How would we identify which completion call this cancellation token relates to ?
Do you know in which order VSCode sends completion and cancellation token ?
I added some logging and spammed a bunch of requests. From what I can tell vscode will always cancel the previous request before firing a new one.
[TESTING: completion request]
[TESTING: cancelation event fired]
[TESTING: completion request]
[TESTING: cancelation event fired]
[TESTING: completion request]
[TESTING: cancelation event fired]
[TESTING: completion request]
[TESTING: cancelation event fired]
[TESTING: completion request]
[TESTING: cancelation event fired]
[TESTING: completion request]
[TESTING: cancelation event fired]
[TESTING: completion request]
[TESTING: cancelation event fired]
[TESTING: completion request]
[TESTING: cancelation event fired]
[TESTING: completion request]
How would we identify which completion call this cancellation token relates to ?
I'm open to whatever you think is best, but it would be easier (for me) if I could provide an 'id' or 'label' string. That way I could generate a guid
and pass it into the listCompletions
request. Maybe something like this:
--action=listCompletions --id='123abc'
Then I could just pass that same id
into the cancelation request
--action=cancelCompletions --id='123abc'
The reason this is easier for me is that I could define the ID as soon as I get a completion request and register the cancelation event immediately. If tooling-force
assigned an id and returned it in the stream, I'd have to delay the event registration until I could parse it.
public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.CompletionItem[]>{
let id = generateGuid();
token.onCancellationRequested(() => {
this.toolingService.cancelCompletions(id);
}
);
return this.toolingService.listCompletions(id, document, position, token);
}
Alternatively, I think a solution that just cancels ALL open completion requests would work (although there might be a race condition that I'm not considering if I sent an --action=cancelCompletions
followed directly by an --action=listCompletions
).
Both "cancel with Id" and "cancel all open" sound like potential candidates. I will need to do some testing. "Id based" one does seem to be more precise at first glance, but the fact that backend would have to keep track of this Id at every turn may be problematic. Thanks for the suggestions.
Hello @ChuckJonas
Can you share an example of a sequence of steps which leads to those long response times you specified in your original message on this issue ?
Sending CMD: --action=listCompletions
Sending CMD: --action=listCompletions
Sending CMD: --action=listCompletions
Sending CMD: --action=listCompletions
[INFO] Executor - # Time taken: 20.004s
[INFO] Executor - # Time taken: 20.004s
[INFO] Executor - # Time taken: 20.005s
[INFO] Executor - # Time taken: 15.032s
Thanks.
@neowit ya, so basically vscode automatically handles the triggering of "CompletionRequersts". You can specify a trigger character, (like .
) but vscode still might send request in other contexts (not sure why).
I've created a screen capture that demonstrates this:
In this case above, the lag is due to the latency in the original network request to get meta-data information for the account.
However, this is similar to what I'll see after running the service for 10-20 minutes. For some reason (I haven't been able to identify a consistent pattern), the requests start to queue up and when that happens they start taking exponentially longer to return (likely due to shared memory/cpu). Basically once it happens the server will never catch up unless you walk away for half an hour (I just restart it)
Hey,
Just wondering how you handle the queueing up of multiple
listCompletions
requests. I'm noticing that if I queue up several requests back to back, the service starts to take longer and longer to respond (likely running out of memory?).I figure this is something that you've handled in your VIM plugin. Is there someway to cancel the previous
listCompletion
when new ones come in?Here what my plugin debugs end up looking like to help you visualize the problem I'm running into.