Browse Source

fix: resolve Float64Array concurrency, out of bounds, and cache invalidation race conditions

pull/6901/head
Andrea Bugeja 1 week ago
parent
commit
3d03bf2866
  1. 10
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
  2. 15
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts
  3. 25
      apps/api/src/services/market-data/market-data.service.ts

10
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

@ -240,7 +240,9 @@ describe('PortfolioCalculator', () => {
feeInBaseCurrency: new Big(0), feeInBaseCurrency: new Big(0),
grossPerformance: new Big(0), grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0), grossPerformancePercentage: new Big(0),
grossPerformancePercentageWithCurrencyEffect: expect.any(Big), grossPerformancePercentageWithCurrencyEffect: new Big(
'0.08211538461538461533'
),
grossPerformanceWithCurrencyEffect: new Big(70), grossPerformanceWithCurrencyEffect: new Big(70),
includeInTotalAssetValue: false, includeInTotalAssetValue: false,
investment: new Big(1820), investment: new Big(1820),
@ -269,8 +271,10 @@ describe('PortfolioCalculator', () => {
}, },
quantity: new Big(2000), quantity: new Big(2000),
symbol: 'USD', symbol: 'USD',
timeWeightedInvestment: expect.any(Big), timeWeightedInvestment: new Big('912.48633879781420820235'),
timeWeightedInvestmentWithCurrencyEffect: expect.any(Big), timeWeightedInvestmentWithCurrencyEffect: new Big(
'852.4590163934426234665'
),
valueInBaseCurrency: new Big(1820) valueInBaseCurrency: new Big(1820)
}); });

15
apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

@ -362,7 +362,7 @@ export class ExchangeRateDataService {
} }
private getDaysSinceEpoch(aDate: Date) { private getDaysSinceEpoch(aDate: Date) {
return Math.floor(aDate.getTime() / 86400000); return Math.floor(aDate.getTime() / 86400000) + 25569;
} }
private async loadCache(aSymbol: string): Promise<Float64Array> { private async loadCache(aSymbol: string): Promise<Float64Array> {
@ -374,7 +374,12 @@ export class ExchangeRateDataService {
}); });
const todayDays = this.getDaysSinceEpoch(new Date()); const todayDays = this.getDaysSinceEpoch(new Date());
const array = new Float64Array(todayDays + 1); const maxDataDays =
marketData.length > 0
? this.getDaysSinceEpoch(marketData[marketData.length - 1].date)
: 0;
const array = new Float64Array(Math.max(todayDays, maxDataDays) + 1);
if (marketData.length > 0) { if (marketData.length > 0) {
let lastRate = marketData[0].marketPrice; let lastRate = marketData[0].marketPrice;
@ -404,14 +409,10 @@ export class ExchangeRateDataService {
): Promise<number | undefined> { ): Promise<number | undefined> {
let cache = this.exchangeRateCache.get(aSymbol); let cache = this.exchangeRateCache.get(aSymbol);
if (!cache) { while (!cache) {
if (this.pendingLoads.has(aSymbol)) { if (this.pendingLoads.has(aSymbol)) {
await this.pendingLoads.get(aSymbol); await this.pendingLoads.get(aSymbol);
cache = this.exchangeRateCache.get(aSymbol); cache = this.exchangeRateCache.get(aSymbol);
if (!cache) {
cache = await this.loadAndCommit(aSymbol);
}
} else { } else {
cache = await this.loadAndCommit(aSymbol); cache = await this.loadAndCommit(aSymbol);
} }

25
apps/api/src/services/market-data/market-data.service.ts

@ -22,13 +22,16 @@ export class MarketDataService {
) {} ) {}
public async deleteMany({ dataSource, symbol }: AssetProfileIdentifier) { public async deleteMany({ dataSource, symbol }: AssetProfileIdentifier) {
this.eventEmitter.emit('market-data.updated', { symbol }); const result = await this.prismaService.marketData.deleteMany({
return this.prismaService.marketData.deleteMany({
where: { where: {
dataSource, dataSource,
symbol symbol
} }
}); });
this.eventEmitter.emit('market-data.updated', { symbol });
return result;
} }
public async get({ public async get({
@ -198,14 +201,7 @@ export class MarketDataService {
oldAssetProfileIdentifier: AssetProfileIdentifier, oldAssetProfileIdentifier: AssetProfileIdentifier,
newAssetProfileIdentifier: AssetProfileIdentifier newAssetProfileIdentifier: AssetProfileIdentifier
) { ) {
this.eventEmitter.emit('market-data.updated', { const result = await this.prismaService.marketData.updateMany({
symbol: oldAssetProfileIdentifier.symbol
});
this.eventEmitter.emit('market-data.updated', {
symbol: newAssetProfileIdentifier.symbol
});
return this.prismaService.marketData.updateMany({
data: { data: {
dataSource: newAssetProfileIdentifier.dataSource, dataSource: newAssetProfileIdentifier.dataSource,
symbol: newAssetProfileIdentifier.symbol symbol: newAssetProfileIdentifier.symbol
@ -215,6 +211,15 @@ export class MarketDataService {
symbol: oldAssetProfileIdentifier.symbol symbol: oldAssetProfileIdentifier.symbol
} }
}); });
this.eventEmitter.emit('market-data.updated', {
symbol: oldAssetProfileIdentifier.symbol
});
this.eventEmitter.emit('market-data.updated', {
symbol: newAssetProfileIdentifier.symbol
});
return result;
} }
public async updateMarketData(params: { public async updateMarketData(params: {

Loading…
Cancel
Save