google-api-dotnet-client: Authenticating / Refreshing Google API access token and avoiding a “file in use” exception?
I have been encouraged to ask my question that I raised on StackOverflow here.
In short, I have been having occasional issues when trying to sync with a Google Calendar.
The basic functionality is:
- Authenticate
- Delete existing calendar events
- Add new calendar events
The delete / add tasks are done using batching.
As mentioned, sometimes I get an exception like:
2020-11-30 18:47:03.6762|ERROR|GoogleAuthandSync.Program|AddEventsToCalendarXML|System.IO.IOException: The process cannot access the file ‘C:\Users\USERNAME\AppData\Roaming\XXXXX.Application\Google.Apis.Auth.OAuth2.Responses.TokenResponse-user’ because it is being used by another process. at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
This is how I authenticate:
Private Function DoAuthentication(ByRef rStrToken As String, ByRef rParameters As OAuth2Parameters) As Boolean
Dim credential As UserCredential
Dim Secrets = New ClientSecrets() With {
.ClientId = m_strClientID,
.ClientSecret = m_strClientSecret
}
m_Scopes.Add(CalendarService.Scope.Calendar)
Try
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(Secrets, m_Scopes,
"user", CancellationToken.None,
New FileDataStore("XXXXX.Application")).Result()
If credential.Token.IsExpired(Google.Apis.Util.SystemClock.Default) Then
credential.RefreshTokenAsync(CancellationToken.None)
End If
' Create the calendar service using an initializer instance
Dim initializer As New BaseClientService.Initializer() With {
.HttpClientInitializer = credential,
.ApplicationName = "xxx"
}
m_Service = New CalendarService(initializer)
rStrToken = credential.Token.AccessToken.ToString()
rParameters.AccessToken = credential.Token.AccessToken
rParameters.RefreshToken = credential.Token.RefreshToken
Catch ex As Exception
' We encountered some kind of problem, perhaps they have not yet authenticated?
' Can we isolate that as the exception?
m_logger.Error(ex, "DoAuthentication")
Return False
End Try
Return True
End Function
I was wondering if this line is wrong:
credential.RefreshTokenAsync(CancellationToken.None)
Should it be proceeded with await
? I wondered if it might still be refreshing the token file whilst we are continuing to try and use it. I can provide further code snippets as needed.
As mentioned, it usually works and this is a sporadic issue.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 1
- Comments: 24 (5 by maintainers)
You don’t need to do any of that, there’s a Revoke method on the UserCredential. You can do something like the following (I’m writing directly on the comment so there might be some syntax errors).
This will both revoke the token with the Google Auth platform and delete it from the local file.
Yes, given that yours is a console application, it should be fine as it is now. Just calling Wait() and Result is redundant. You can just do:
It’s always better to do Await, calling Result will block the calling thread and could in some cases lead to deadlocks. Here is some starter docs on Asynchronous programming in .NET
You said yourself you are not using those, right? The following line is only creating an instance of the
RequestSettings
object, if you don’t usesettings
anywhere it will have no effect, so in that case it is safe to delete them.You should generally not use the
Result
property from tasks. That blocks the current thread until the task has completed - which means if the task is asynchronous and expects to be able to get back to the current thread (e.g. in a WinForms app) it creates a deadlock.Instead, you should make this calling method async as well, and await the task.
Yes,
Async
methods can’t haveByRef
parameters, but than can be easily solved, if you needed it, by returning a more complex type.But, as @jskeet suggested, I would advise you stop returning and writing in a file the tokens yourself, and also refreshing the tokens. Your authenticate method will turn into something like this:
And then, as long as you are using
m_service
in methods such asAddEventsToCalendarXML
you don’t need to read the tokens from your own file.m_service
has already been initialized with the credential, which stores the tokens in"XXXXX.Application"
and will always providem_service
with a fresh token, because it checks the token in the file for freshnes and refreshes it if needed. So, although I’m not sure I have a full understanding of your app, I think you can remove the authenticate switch and always callawait DoAuthenticationAsync()
as a first step for all the other switches to guarantee that you havem_service
correctly initialized with the credential.You can see a C# example here in the method called
Run
(it uses the Book API but that’s the same as using the Calendar API).Your suspicion is right, definetely
credential.RefreshTokenAsync(CancellationToken.None)
should be awaited else, yes, there might be file clashes if the token had expired or was close to expiring, because whenm_service
requests a token from the credential the credential will also attempt to refresh the token to guarantee thatm_service
always gets a fresh one. And also, there’s no guarantee that the token you get in this linerParameters.AccessToken = credential.Token.AccessToken
is the fresh token if refreshing is not done yet. (And async methods should always be awaited).You’ll have to turn your method into an async method, like such:
I would also recommend to await the call to
AuthorizeAsync
instead of calling.Result()
which will block the thread (and in some cases could lead to deadlocks):Also, I see that you are returning the tokens as part of your method, so I suppose you are using them somewhere else and need them fresh? If I may ask, what are you using them for, since you’ve already instantiated the CalendarService using the credential? If you don’t need them for anything else, you don’t need to refresh the token explicitly here, when the calendar service requests a token from the credential, the credential will check whether the token has expired or is close to expiring and will refresh it and always give you a fresh one.
I would suggest avoiding writing out your own authentication file. You’re already using an auth file here:
New FileDataStore("XXXXX.Application")
. Just use that, always.That authentication flow will (IIRC) detect if the file exists and use the existing authentication if it’s present and valid for the scopes you’re requesting, or prompt the user if necessary. There’s no need to do anything more, as far as I can see.
But if you’re using
m_Service
, that should refresh automatically. We can’t tell from the sample what you’re doing withrStrToken
orrParameters
- in most cases you shouldn’t need to care about the access/refresh token yourself at all. Just load the credential, create the service with it, and everything should happen transparently.I suspect the problem may be caused by calling
RefreshTokenAsync
and then not waiting for it to complete. (You should also await theAuthorizeAsync
call instead of using theResult
property.) But more importantly, you shouldn’t need to do any of that part yourself at all. It’s not clear to me why you’re storing the access token and refresh token - you’re already creating the service with the credential appropriately, and the credential will just refresh its access token automatically as and when it needs to.I would have hoped that the library would be thread-safe and not try to perform multiple operations on the file anyway, and we should look into that, but I’m also hoping that the code given can be simplified to remove the manual refresh.
@amanda-tarafa i forwarded them over here. I have never seen this issue before with the library. In my experience the file lock is always released after it reads it so i’m not sure why the lock would remain. However my knowledge of this library in VB.net is very limited