mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
[PM-1576] Fix Race condition AccountsManager registration (#2434)
* PM-1576 Moved registration of AccountsManager to avoid race conditions with the app start. To do so, added ConditionedAwaiterManager so that it handles a task to be awaited or completed depending on the callers. * PM-1576 Fix format * PM-1576 Fix throw to preserve StackTrace
This commit is contained in:
parent
e5ce1760a6
commit
1823efa0e5
7 changed files with 132 additions and 37 deletions
|
@ -81,7 +81,8 @@ namespace Bit.Droid
|
||||||
ServiceContainer.Resolve<IAuthService>("authService"),
|
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||||
ServiceContainer.Resolve<ILogger>("logger"),
|
ServiceContainer.Resolve<ILogger>("logger"),
|
||||||
ServiceContainer.Resolve<IMessagingService>("messagingService"),
|
ServiceContainer.Resolve<IMessagingService>("messagingService"),
|
||||||
ServiceContainer.Resolve<IWatchDeviceService>());
|
ServiceContainer.Resolve<IWatchDeviceService>(),
|
||||||
|
ServiceContainer.Resolve<IConditionedAwaiterManager>());
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace Bit.App.Utilities.AccountManagement
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IWatchDeviceService _watchDeviceService;
|
private readonly IWatchDeviceService _watchDeviceService;
|
||||||
|
private readonly IConditionedAwaiterManager _conditionedAwaiterManager;
|
||||||
|
|
||||||
Func<AppOptions> _getOptionsFunc;
|
Func<AppOptions> _getOptionsFunc;
|
||||||
private IAccountsManagerHost _accountsManagerHost;
|
private IAccountsManagerHost _accountsManagerHost;
|
||||||
|
@ -34,7 +35,8 @@ namespace Bit.App.Utilities.AccountManagement
|
||||||
IAuthService authService,
|
IAuthService authService,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IMessagingService messagingService,
|
IMessagingService messagingService,
|
||||||
IWatchDeviceService watchDeviceService)
|
IWatchDeviceService watchDeviceService,
|
||||||
|
IConditionedAwaiterManager conditionedAwaiterManager)
|
||||||
{
|
{
|
||||||
_broadcasterService = broadcasterService;
|
_broadcasterService = broadcasterService;
|
||||||
_vaultTimeoutService = vaultTimeoutService;
|
_vaultTimeoutService = vaultTimeoutService;
|
||||||
|
@ -45,6 +47,7 @@ namespace Bit.App.Utilities.AccountManagement
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_messagingService = messagingService;
|
_messagingService = messagingService;
|
||||||
_watchDeviceService = watchDeviceService;
|
_watchDeviceService = watchDeviceService;
|
||||||
|
_conditionedAwaiterManager = conditionedAwaiterManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
||||||
|
@ -59,6 +62,8 @@ namespace Bit.App.Utilities.AccountManagement
|
||||||
|
|
||||||
public async Task StartDefaultNavigationFlowAsync(Action<AppOptions> appOptionsAction)
|
public async Task StartDefaultNavigationFlowAsync(Action<AppOptions> appOptionsAction)
|
||||||
{
|
{
|
||||||
|
await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
|
|
||||||
appOptionsAction(Options);
|
appOptionsAction(Options);
|
||||||
|
|
||||||
await NavigateOnAccountChangeAsync();
|
await NavigateOnAccountChangeAsync();
|
||||||
|
@ -66,6 +71,8 @@ namespace Bit.App.Utilities.AccountManagement
|
||||||
|
|
||||||
public async Task NavigateOnAccountChangeAsync(bool? isAuthed = null)
|
public async Task NavigateOnAccountChangeAsync(bool? isAuthed = null)
|
||||||
{
|
{
|
||||||
|
await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
|
|
||||||
// TODO: this could be improved by doing chain of responsability pattern
|
// TODO: this could be improved by doing chain of responsability pattern
|
||||||
// but for now it may be an overkill, if logic gets more complex consider refactoring it
|
// but for now it may be an overkill, if logic gets more complex consider refactoring it
|
||||||
|
|
||||||
|
@ -132,6 +139,8 @@ namespace Bit.App.Utilities.AccountManagement
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
|
|
||||||
switch (message.Command)
|
switch (message.Command)
|
||||||
{
|
{
|
||||||
case AccountsManagerMessageCommands.LOCKED:
|
case AccountsManagerMessageCommands.LOCKED:
|
||||||
|
@ -206,6 +215,8 @@ namespace Bit.App.Utilities.AccountManagement
|
||||||
|
|
||||||
public async Task LogOutAsync(string userId, bool userInitiated, bool expired)
|
public async Task LogOutAsync(string userId, bool userInitiated, bool expired)
|
||||||
{
|
{
|
||||||
|
await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
|
|
||||||
await AppHelpers.LogOutAsync(userId, userInitiated);
|
await AppHelpers.LogOutAsync(userId, userInitiated);
|
||||||
await NavigateOnAccountChangeAsync();
|
await NavigateOnAccountChangeAsync();
|
||||||
_authService.LogOut(() =>
|
_authService.LogOut(() =>
|
||||||
|
@ -244,6 +255,8 @@ namespace Bit.App.Utilities.AccountManagement
|
||||||
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
|
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
|
||||||
if (switchToAccount)
|
if (switchToAccount)
|
||||||
{
|
{
|
||||||
|
await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
|
|
||||||
await _stateService.SetActiveUserAsync(userId);
|
await _stateService.SetActiveUserAsync(userId);
|
||||||
_messagingService.Send("switchedAccount");
|
_messagingService.Send("switchedAccount");
|
||||||
}
|
}
|
||||||
|
|
17
src/Core/Abstractions/IConditionedAwaiterManager.cs
Normal file
17
src/Core/Abstractions/IConditionedAwaiterManager.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public enum AwaiterPrecondition
|
||||||
|
{
|
||||||
|
EnvironmentUrlsInited
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IConditionedAwaiterManager
|
||||||
|
{
|
||||||
|
Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition);
|
||||||
|
void SetAsCompleted(AwaiterPrecondition awaiterPrecondition);
|
||||||
|
void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex);
|
||||||
|
}
|
||||||
|
}
|
42
src/Core/Services/ConditionedAwaiterManager.cs
Normal file
42
src/Core/Services/ConditionedAwaiterManager.cs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services
|
||||||
|
{
|
||||||
|
public class ConditionedAwaiterManager : IConditionedAwaiterManager
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<AwaiterPrecondition, TaskCompletionSource<bool>> _preconditionsTasks = new ConcurrentDictionary<AwaiterPrecondition, TaskCompletionSource<bool>>
|
||||||
|
{
|
||||||
|
[AwaiterPrecondition.EnvironmentUrlsInited] = new TaskCompletionSource<bool>()
|
||||||
|
};
|
||||||
|
|
||||||
|
public Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition)
|
||||||
|
{
|
||||||
|
if (_preconditionsTasks.TryGetValue(awaiterPrecondition, out var tcs))
|
||||||
|
{
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAsCompleted(AwaiterPrecondition awaiterPrecondition)
|
||||||
|
{
|
||||||
|
if (_preconditionsTasks.TryGetValue(awaiterPrecondition, out var tcs))
|
||||||
|
{
|
||||||
|
tcs.TrySetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex)
|
||||||
|
{
|
||||||
|
if (_preconditionsTasks.TryGetValue(awaiterPrecondition, out var tcs))
|
||||||
|
{
|
||||||
|
tcs.TrySetException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
|
@ -13,13 +14,16 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
private readonly IApiService _apiService;
|
private readonly IApiService _apiService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IConditionedAwaiterManager _conditionedAwaiterManager;
|
||||||
|
|
||||||
public EnvironmentService(
|
public EnvironmentService(
|
||||||
IApiService apiService,
|
IApiService apiService,
|
||||||
IStateService stateService)
|
IStateService stateService,
|
||||||
|
IConditionedAwaiterManager conditionedAwaiterManager)
|
||||||
{
|
{
|
||||||
_apiService = apiService;
|
_apiService = apiService;
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
|
_conditionedAwaiterManager = conditionedAwaiterManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string BaseUrl { get; set; }
|
public string BaseUrl { get; set; }
|
||||||
|
@ -52,30 +56,44 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
public async Task SetUrlsFromStorageAsync()
|
public async Task SetUrlsFromStorageAsync()
|
||||||
{
|
{
|
||||||
var urls = await _stateService.GetEnvironmentUrlsAsync();
|
try
|
||||||
if (urls == null)
|
|
||||||
{
|
{
|
||||||
urls = await _stateService.GetPreAuthEnvironmentUrlsAsync();
|
var urls = await _stateService.GetEnvironmentUrlsAsync();
|
||||||
}
|
if (urls == null)
|
||||||
if (urls == null)
|
{
|
||||||
{
|
urls = await _stateService.GetPreAuthEnvironmentUrlsAsync();
|
||||||
urls = new EnvironmentUrlData();
|
}
|
||||||
}
|
if (urls == null)
|
||||||
var envUrls = new EnvironmentUrls();
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(urls.Base))
|
urls = new EnvironmentUrlData();
|
||||||
{
|
}
|
||||||
BaseUrl = envUrls.Base = urls.Base;
|
var envUrls = new EnvironmentUrls();
|
||||||
|
if (!string.IsNullOrWhiteSpace(urls.Base))
|
||||||
|
{
|
||||||
|
BaseUrl = envUrls.Base = urls.Base;
|
||||||
|
_apiService.SetUrls(envUrls);
|
||||||
|
|
||||||
|
_conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseUrl = urls.Base;
|
||||||
|
WebVaultUrl = urls.WebVault;
|
||||||
|
ApiUrl = envUrls.Api = urls.Api;
|
||||||
|
IdentityUrl = envUrls.Identity = urls.Identity;
|
||||||
|
IconsUrl = urls.Icons;
|
||||||
|
NotificationsUrl = urls.Notifications;
|
||||||
|
EventsUrl = envUrls.Events = urls.Events;
|
||||||
_apiService.SetUrls(envUrls);
|
_apiService.SetUrls(envUrls);
|
||||||
return;
|
|
||||||
|
_conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
}
|
}
|
||||||
BaseUrl = urls.Base;
|
catch (System.Exception ex)
|
||||||
WebVaultUrl = urls.WebVault;
|
{
|
||||||
ApiUrl = envUrls.Api = urls.Api;
|
_conditionedAwaiterManager.SetException(AwaiterPrecondition.EnvironmentUrlsInited, ex);
|
||||||
IdentityUrl = envUrls.Identity = urls.Identity;
|
throw;
|
||||||
IconsUrl = urls.Icons;
|
}
|
||||||
NotificationsUrl = urls.Notifications;
|
|
||||||
EventsUrl = envUrls.Events = urls.Events;
|
|
||||||
_apiService.SetUrls(envUrls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EnvironmentUrlData> SetUrlsAsync(EnvironmentUrlData urls)
|
public async Task<EnvironmentUrlData> SetUrlsAsync(EnvironmentUrlData urls)
|
||||||
|
|
|
@ -33,6 +33,7 @@ namespace Bit.Core.Utilities
|
||||||
|
|
||||||
SearchService searchService = null;
|
SearchService searchService = null;
|
||||||
|
|
||||||
|
var conditionedRunner = new ConditionedAwaiterManager();
|
||||||
var tokenService = new TokenService(stateService);
|
var tokenService = new TokenService(stateService);
|
||||||
var apiService = new ApiService(tokenService, platformUtilsService, (extras) =>
|
var apiService = new ApiService(tokenService, platformUtilsService, (extras) =>
|
||||||
{
|
{
|
||||||
|
@ -82,12 +83,13 @@ namespace Bit.Core.Utilities
|
||||||
keyConnectorService, passwordGenerationService);
|
keyConnectorService, passwordGenerationService);
|
||||||
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
||||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||||
var environmentService = new EnvironmentService(apiService, stateService);
|
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
|
||||||
var eventService = new EventService(apiService, stateService, organizationService, cipherService);
|
var eventService = new EventService(apiService, stateService, organizationService, cipherService);
|
||||||
var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService,
|
var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService,
|
||||||
cryptoService);
|
cryptoService);
|
||||||
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
|
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
|
||||||
|
|
||||||
|
Register<IConditionedAwaiterManager>(conditionedRunner);
|
||||||
Register<ITokenService>("tokenService", tokenService);
|
Register<ITokenService>("tokenService", tokenService);
|
||||||
Register<IApiService>("apiService", apiService);
|
Register<IApiService>("apiService", apiService);
|
||||||
Register<IAppIdService>("appIdService", appIdService);
|
Register<IAppIdService>("appIdService", appIdService);
|
||||||
|
|
|
@ -156,6 +156,20 @@ namespace Bit.iOS.Core.Utilities
|
||||||
ServiceContainer.Resolve<IAuthService>("authService").Init();
|
ServiceContainer.Resolve<IAuthService>("authService").Init();
|
||||||
(ServiceContainer.
|
(ServiceContainer.
|
||||||
Resolve<IPlatformUtilsService>("platformUtilsService") as MobilePlatformUtilsService).Init();
|
Resolve<IPlatformUtilsService>("platformUtilsService") as MobilePlatformUtilsService).Init();
|
||||||
|
|
||||||
|
var accountsManager = new AccountsManager(
|
||||||
|
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
|
||||||
|
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
|
||||||
|
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||||
|
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||||
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
|
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||||
|
ServiceContainer.Resolve<ILogger>("logger"),
|
||||||
|
ServiceContainer.Resolve<IMessagingService>("messagingService"),
|
||||||
|
ServiceContainer.Resolve<IWatchDeviceService>(),
|
||||||
|
ServiceContainer.Resolve<IConditionedAwaiterManager>());
|
||||||
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
|
|
||||||
// Note: This is not awaited
|
// Note: This is not awaited
|
||||||
var bootstrapTask = BootstrapAsync(postBootstrapFunc);
|
var bootstrapTask = BootstrapAsync(postBootstrapFunc);
|
||||||
}
|
}
|
||||||
|
@ -235,18 +249,6 @@ namespace Bit.iOS.Core.Utilities
|
||||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||||
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
||||||
|
|
||||||
var accountsManager = new AccountsManager(
|
|
||||||
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
|
|
||||||
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
|
|
||||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
|
||||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
|
||||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
|
||||||
ServiceContainer.Resolve<IAuthService>("authService"),
|
|
||||||
ServiceContainer.Resolve<ILogger>("logger"),
|
|
||||||
ServiceContainer.Resolve<IMessagingService>("messagingService"),
|
|
||||||
ServiceContainer.Resolve<IWatchDeviceService>());
|
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
|
||||||
|
|
||||||
if (postBootstrapFunc != null)
|
if (postBootstrapFunc != null)
|
||||||
{
|
{
|
||||||
await postBootstrapFunc.Invoke();
|
await postBootstrapFunc.Invoke();
|
||||||
|
|
Loading…
Reference in a new issue