Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: proxy mode #571

Draft
wants to merge 42 commits into
base: master
Choose a base branch
from
Draft

feat: proxy mode #571

wants to merge 42 commits into from

Conversation

hezhengxu2018
Copy link
Collaborator

@hezhengxu2018 hezhengxu2018 commented Aug 16, 2023

#366 开启代理模式时如果找不到依赖会直接返回上游仓库的manifest信息并缓存于nfs,当请求的tgz文件不存在时从上游仓库获取并返回,同时创建对应版本的同步任务。每小时检查更新已缓存的manifest文件保证上游仓库发布新版本时不会因为缓存落后而404。

Summary by CodeRabbit

  • New Features
    • Introduced ProxyCache functionality for caching and retrieving package manifests.
    • Added new SyncMode.proxy for enhanced synchronization operations.
    • Implemented scheduled tasks for updating and synchronizing proxy cache.
  • Refactor
    • Improved clarity and reusability in manifest retrieval methods.
    • Restructured configuration types to support new proxy cache features.
  • Bug Fixes
    • Enhanced error handling in package download and display based on sync mode.
  • Documentation
    • Updated constant and enum values to support new features.
  • Tests
    • Added comprehensive tests for new features and refactored methods.

@hezhengxu2018 hezhengxu2018 marked this pull request as draft August 16, 2023 06:31
@codecov
Copy link

codecov bot commented Aug 16, 2023

Codecov Report

Attention: Patch coverage is 93.97042% with 53 lines in your changes are missing coverage. Please review.

Project coverage is 96.68%. Comparing base (049b186) to head (68b26f7).
Report is 8 commits behind head on master.

❗ Current head 68b26f7 differs from pull request most recent head d79ec58. Consider uploading reports for the commit d79ec58 to get more accurate results

Files Patch % Lines
app/core/service/ProxyCacheService.ts 86.72% 30 Missing ⚠️
...controller/package/ShowPackageVersionController.ts 79.16% 5 Missing ⚠️
app/port/schedule/SyncProxyCacheWorker.ts 91.07% 5 Missing ⚠️
...p/port/controller/package/ShowPackageController.ts 84.61% 4 Missing ⚠️
app/repository/ProxyCacheRepository.ts 95.08% 3 Missing ⚠️
app/core/entity/Task.ts 95.23% 2 Missing ⚠️
app/port/controller/ProxyCacheController.ts 98.71% 2 Missing ⚠️
app/port/schedule/CheckProxyCacheUpdateWorker.ts 96.22% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #571      +/-   ##
==========================================
- Coverage   96.83%   96.68%   -0.16%     
==========================================
  Files         180      187       +7     
  Lines       17639    18436     +797     
  Branches     2295     2420     +125     
==========================================
+ Hits        17080    17824     +744     
- Misses        559      612      +53     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hezhengxu2018 hezhengxu2018 changed the title [WIP] feat: proxy mode feat: proxy mode Aug 18, 2023
@hezhengxu2018 hezhengxu2018 marked this pull request as ready for review August 18, 2023 02:44
@hezhengxu2018
Copy link
Collaborator Author

目前代理模式无法配置上游仓库认证头的信息,认证头应该作为仓库的一个属性记录在数据库里,可以通过接口修改。

@elrrrrrrr
Copy link
Member

@hezhengxu2018 🤩 已经改完了吗?

@hezhengxu2018
Copy link
Collaborator Author

@hezhengxu2018 🤩 已经改完了吗?

是的,现在会定时更新已缓存的manifest,不过还没有手动管理缓存的接口。verdicca和nexus都没做这个,先基础功能确定没问题再做吧

@hezhengxu2018 hezhengxu2018 marked this pull request as draft December 12, 2023 14:37
Copy link

socket-security bot commented Dec 23, 2023

New and removed dependencies detected. Learn more about Socket for GitHub ↗︎

Package New capabilities Transitives Size Publisher
npm/@eggjs/http-proxy@1.1.1 Transitive: environment, filesystem, shell +7 151 kB fengmk2

View full report↗︎

@hezhengxu2018 hezhengxu2018 marked this pull request as ready for review December 24, 2023 15:43
@hezhengxu2018
Copy link
Collaborator Author

管理缓存的接口也加上了,功能相对来说比较完整了,帮忙看一下吧 @elrrrrrrr @fengmk2

@elrrrrrrr
Copy link
Member

管理缓存的接口也加上了,功能相对来说比较完整了,帮忙看一下吧 @elrrrrrrr @fengmk2

🤩 改动内容比较多 我明天详细看下 🙏🏻

pkg = await this.getPackageEntityByFullname(fullname, allowSync);
packageVersion = await this.getPackageVersionEntity(pkg, version, allowSync);
} catch (error) {
if (this.config.cnpmcore.syncMode === SyncMode.proxy) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里直接用 AOP 来组织一下逻辑会清晰一下,类似 EventCorkAdvice 的实现

在原来方法返回前,判断一下是否有返回值,如果没有的话再去触发 proxyMode 的 fallback 逻辑

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤯

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

尝试了一下好像没有改写函数返回的切点?这样的话确实可以较为优雅的处理代理的逻辑但是改不了接口的返回值。用户还是会收到not found的返回。还是希望代理模式的表现和nexus与verdaccio保持一致。

@elrrrrrrr
Copy link
Member

@hezhengxu2018

proxyMode 定位是做代理模式,然后本地 registry 做缓存加速?需要确认一下 manifest 以本地为准还是以上游为准。

目前实现是 manifest 和 tgz 请求时,将代理结果直接返回,并创建同步任务

  1. proxyMode 下,对于
  2. 优先返回 本地 registry 信息
  3. 若本地没有版本数据,则代理返回上游 registry
  4. tgz 访问时触发下载,仅同步当前访问报的单个版本

如果 proxyMode 中直接命中 tgz 下载,需要等异步定时任务补偿后才能继续访问,否则 manifest 会返回单个版本?客户单查包信息的时候就过期了。

我们是否可以改为 proxyMode 下,始终返回上游 registry 信息已获取更高的实时性。

@hezhengxu2018
Copy link
Collaborator Author

@hezhengxu2018

proxyMode 定位是做代理模式,然后本地 registry 做缓存加速?需要确认一下 manifest 以本地为准还是以上游为准。

目前实现是 manifest 和 tgz 请求时,将代理结果直接返回,并创建同步任务

  1. proxyMode 下,对于
  2. 优先返回 本地 registry 信息
  3. 若本地没有版本数据,则代理返回上游 registry
  4. tgz 访问时触发下载,仅同步当前访问报的单个版本

如果 proxyMode 中直接命中 tgz 下载,需要等异步定时任务补偿后才能继续访问,否则 manifest 会返回单个版本?客户单查包信息的时候就过期了。

我们是否可以改为 proxyMode 下,始终返回上游 registry 信息已获取更高的实时性。

开启proxyMode之后返回的manifest都是上游仓库上次更新的manifest,不会使用数据库里的manifest信息。如果上游仓库无法正常使用了,切回到none模式下才会返回代理仓库已缓存版本的manifest。如果异步任务还没同步完成会一直通过反向代理返回上游仓库的tgz信息,用户不会需要等待异步任务完成,当异步任务完成后才会优先从对象存储中读取tgz。

代理仓库主要的功能点是在访问外网或者官方npm仓库非常缓慢甚至无法访问的情况下能够缓存结果加速内网用户安装依赖,同时即使外网无法访问了也不影响内网用户照常使用已经缓存的依赖,所以始终返回上游仓库可能不行,如果内网用户发现代理仓库的缓存需要更新或者缓存脏数据了可以使用/-/proxy-cache接口进行一些缓存的刷新删除。

@hezhengxu2018 hezhengxu2018 marked this pull request as draft December 25, 2023 16:08
@hezhengxu2018
Copy link
Collaborator Author

manifest 以本地为准,因为nexus是这么做的。代理模式整体是一个缓存,如果网络不好还一直使用上游仓库的索引的话就没有缓存的意义了,不过nexus默认刷新manifest的频率很高,30分钟就会去刷新一次,我设置每天刷新一次感觉有点保守了。

Copy link
Contributor

coderabbitai bot commented Mar 30, 2024

Important

Auto Review Skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This update introduces a robust proxy caching system for package manifests, enhancing the efficiency of data retrieval and management. Key changes include the refactoring of methods for clarity, the introduction of new classes and enums for proxy cache handling, and updates to configurations and error handling to support new sync modes.

Changes

File Path Change Summary
app/common/adapter/NPMRegistry.ts
app/core/service/ProxyCacheService.ts
Refactored and added methods for manifest retrieval and proxy cache management.
app/common/constants.ts
app/port/config.ts
config/plugin.ts
Added new constants and configuration options for proxy caching.
app/common/enum/Task.ts
app/core/entity/Task.ts
app/port/schedule/...
Extended task management to include proxy cache updates.
app/core/entity/ProxyCache.ts
app/repository/ProxyCacheRepository.ts
app/repository/model/ProxyCache.ts
Introduced classes for managing proxy cache data.
app/port/controller/... Enhanced controllers to handle new proxy cache logic and sync modes.
sql/3.47.0.sql Created a new SQL script for proxy cache table.
test/... Added tests covering new functionalities and configurations.

🐰🌟
A hop, a skip, a code deploy,
In the burrow, oh what joy!
Cache the data, swift and sleek,
For faster fetches that we seek.
Cheers to the team, hip hip hooray! 🎉
🐰🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@hezhengxu2018 hezhengxu2018 marked this pull request as ready for review April 10, 2024 09:52
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Comment on lines +123 to +202
describe('getSourceManifestAndCache()', () => {
it('should get full package manifest', async () => {
const data = await TestUtil.readJSONFile(
TestUtil.getFixtures('registry.npmjs.org/foobar.json'),
);
mock(npmRegistry, 'getFullManifests', async () => {
return {
status: 200,
data,
};
});
const { manifest } = await proxyCacheService.getSourceManifestAndCache(
'foobar',
DIST_NAMES.FULL_MANIFESTS,
);
const versionArr = Object.values(manifest.versions);
for (const i of versionArr) {
assert(i.dist.tarball.includes('http://localhost:7001'));
}
});

it('should get abbreviated package manifest', async () => {
const data = await TestUtil.readJSONFile(
TestUtil.getFixtures('registry.npmjs.org/abbreviated_foobar.json'),
);
mock(npmRegistry, 'getAbbreviatedManifests', async () => {
return {
status: 200,
data,
};
});
const { manifest } = await proxyCacheService.getSourceManifestAndCache(
'foobar',
DIST_NAMES.ABBREVIATED_MANIFESTS,
);
const versionArr = Object.values(manifest.versions);
for (const i of versionArr) {
assert(i.dist.tarball.includes('http://localhost:7001'));
}
});

it('should get full package version manifest', async () => {
const data = await TestUtil.readJSONFile(
TestUtil.getFixtures('registry.npmjs.org/foobar/1.0.0/package.json'),
);
mock(npmRegistry, 'getPackageVersionManifest', async () => {
return {
status: 200,
data,
};
});
const { manifest } = await proxyCacheService.getSourceManifestAndCache(
'foobar',
DIST_NAMES.MANIFEST,
'1.0.0',
);
assert(manifest.dist);
assert(manifest.dist.tarball.includes('http://localhost:7001'));
});

it('should get abbreviated package version manifest', async () => {
const data = await TestUtil.readJSONFile(
TestUtil.getFixtures(
'registry.npmjs.org/foobar/1.0.0/abbreviated.json',
),
);
mock(npmRegistry, 'getAbbreviatedPackageVersionManifest', async () => {
return {
status: 200,
data,
};
});
const { manifest } = await proxyCacheService.getSourceManifestAndCache(
'foobar',
DIST_NAMES.ABBREVIATED,
'1.0.0',
);
assert(manifest.dist);
assert(manifest.dist.tarball.includes('http://localhost:7001'));
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

The tests for the getSourceManifestAndCache() method in ProxyCacheService.test.ts do not cover scenarios where the upstream registry returns errors or non-200 status codes. This is confirmed by the absence of any test cases handling status !== 200 in the test file.

Analysis chain

The tests for getSourceManifestAndCache() method effectively cover different manifest types. Ensure that the tests also cover the scenarios where the upstream registry returns errors or non-200 status codes.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Ensure error handling is tested for non-200 responses in getSourceManifestAndCache
rg --type ts "getSourceManifestAndCache" -A 50 | grep "status !== 200"

Length of output: 149



Script:

#!/bin/bash
# Search for test cases handling non-200 status codes in ProxyCacheService.test.ts
rg --type ts "status !== 200" -- "test/core/service/ProxyCacheService.test.ts"

Length of output: 78

Comment on lines 50 to 77
async getPackageManifest(fullname: string, fileType: DIST_NAMES.FULL_MANIFESTS| DIST_NAMES.ABBREVIATED_MANIFESTS): Promise<AbbreviatedPackageManifestType|PackageManifestType> {
const cachedStoreKey = (await this.proxyCacheRepository.findProxyCache(fullname, fileType))?.filePath;
if (cachedStoreKey) {
const nfsBytes = await this.nfsAdapter.getBytes(cachedStoreKey);
if (nfsBytes) {
let nfsPkgManifgest;
try {
const nfsString = Buffer.from(nfsBytes).toString();
nfsPkgManifgest = JSON.parse(nfsString);
} catch {
// JSON parse error
await this.nfsAdapter.remove(cachedStoreKey);
await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
throw new InternalServerError('manifest JSON in NFS parse error');
}
return nfsPkgManifgest;
}
await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
throw new NotFoundError('can not found manifest in NFS.');
}

const { manifest } = await this.getSourceManifestAndCache<typeof fileType>(fullname, fileType);
this.backgroundTaskHelper.run(async () => {
const cachedFiles = ProxyCache.create({ fullname, fileType });
await this.proxyCacheRepository.saveProxyCache(cachedFiles);
});
return manifest;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method getPackageManifest correctly handles caching and fetching from NFS. However, consider adding more robust error handling for NFS operations and JSON parsing.

-          throw new InternalServerError('manifest JSON in NFS parse error');
+          throw new InternalServerError('Failed to parse manifest JSON from NFS. Please check the data integrity.');

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async getPackageManifest(fullname: string, fileType: DIST_NAMES.FULL_MANIFESTS| DIST_NAMES.ABBREVIATED_MANIFESTS): Promise<AbbreviatedPackageManifestType|PackageManifestType> {
const cachedStoreKey = (await this.proxyCacheRepository.findProxyCache(fullname, fileType))?.filePath;
if (cachedStoreKey) {
const nfsBytes = await this.nfsAdapter.getBytes(cachedStoreKey);
if (nfsBytes) {
let nfsPkgManifgest;
try {
const nfsString = Buffer.from(nfsBytes).toString();
nfsPkgManifgest = JSON.parse(nfsString);
} catch {
// JSON parse error
await this.nfsAdapter.remove(cachedStoreKey);
await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
throw new InternalServerError('manifest JSON in NFS parse error');
}
return nfsPkgManifgest;
}
await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
throw new NotFoundError('can not found manifest in NFS.');
}
const { manifest } = await this.getSourceManifestAndCache<typeof fileType>(fullname, fileType);
this.backgroundTaskHelper.run(async () => {
const cachedFiles = ProxyCache.create({ fullname, fileType });
await this.proxyCacheRepository.saveProxyCache(cachedFiles);
});
return manifest;
}
async getPackageManifest(fullname: string, fileType: DIST_NAMES.FULL_MANIFESTS| DIST_NAMES.ABBREVIATED_MANIFESTS): Promise<AbbreviatedPackageManifestType|PackageManifestType> {
const cachedStoreKey = (await this.proxyCacheRepository.findProxyCache(fullname, fileType))?.filePath;
if (cachedStoreKey) {
const nfsBytes = await this.nfsAdapter.getBytes(cachedStoreKey);
if (nfsBytes) {
let nfsPkgManifgest;
try {
const nfsString = Buffer.from(nfsBytes).toString();
nfsPkgManifgest = JSON.parse(nfsString);
} catch {
// JSON parse error
await this.nfsAdapter.remove(cachedStoreKey);
await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
throw new InternalServerError('Failed to parse manifest JSON from NFS. Please check the data integrity.');
}
return nfsPkgManifgest;
}
await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
throw new NotFoundError('can not found manifest in NFS.');
}
const { manifest } = await this.getSourceManifestAndCache<typeof fileType>(fullname, fileType);
this.backgroundTaskHelper.run(async () => {
const cachedFiles = ProxyCache.create({ fullname, fileType });
await this.proxyCacheRepository.saveProxyCache(cachedFiles);
});
return manifest;
}

Comment on lines +168 to +174
async removeProxyCache(fullname: string, fileType: DIST_NAMES, version?: string) {
const storeKey = isPkgManifest(fileType)
? `/${PROXY_CACHE_DIR_NAME}/${fullname}/${fileType}`
: `/${PROXY_CACHE_DIR_NAME}/${fullname}/${version}/${fileType}`;
await this.nfsAdapter.remove(storeKey);
await this.proxyCacheRepository.removeProxyCache(fullname, fileType, version);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method removeProxyCache effectively handles the removal of cache entries. Consider adding logging for successful removals for better traceability.

+ this.logger.info(`Successfully removed cache for ${fullname} with fileType ${fileType}`);

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async removeProxyCache(fullname: string, fileType: DIST_NAMES, version?: string) {
const storeKey = isPkgManifest(fileType)
? `/${PROXY_CACHE_DIR_NAME}/${fullname}/${fileType}`
: `/${PROXY_CACHE_DIR_NAME}/${fullname}/${version}/${fileType}`;
await this.nfsAdapter.remove(storeKey);
await this.proxyCacheRepository.removeProxyCache(fullname, fileType, version);
}
async removeProxyCache(fullname: string, fileType: DIST_NAMES, version?: string) {
const storeKey = isPkgManifest(fileType)
? `/${PROXY_CACHE_DIR_NAME}/${fullname}/${fileType}`
: `/${PROXY_CACHE_DIR_NAME}/${fullname}/${version}/${fileType}`;
await this.nfsAdapter.remove(storeKey);
await this.proxyCacheRepository.removeProxyCache(fullname, fileType, version);
this.logger.info(`Successfully removed cache for ${fullname} with fileType ${fileType}`);
}

Comment on lines 176 to 224
async createTask(targetName: string, options: UpdateProxyCacheTaskOptions): Promise<CreateUpdateProxyCacheTask> {
return await this.taskService.createTask(Task.createUpdateProxyCache(targetName, options), false) as CreateUpdateProxyCacheTask;
}

async findExecuteTask() {
return await this.taskService.findExecuteTask(TaskType.UpdateProxyCache);
}

async executeTask(task: Task) {
const logs: string[] = [];
const fullname = (task as CreateUpdateProxyCacheTask).data.fullname;
const { fileType, version } = (task as CreateUpdateProxyCacheTask).data;
let cacheBytes;
logs.push(`[${isoNow()}] 🚧🚧🚧🚧🚧 Start update "${fullname}-${fileType}" 🚧🚧🚧🚧🚧`);
try {
if (isPkgManifest(fileType)) {
const cachedFiles = await this.proxyCacheRepository.findProxyCache(fullname, fileType);
if (!cachedFiles) throw new Error('task params error, can not found record in repo.');
cacheBytes = (await this.getSourceManifestAndCache<typeof fileType>(fullname, fileType)).proxyBytes;
ProxyCache.update(cachedFiles);
await this.proxyCacheRepository.saveProxyCache(cachedFiles);
} else {
task.error = 'Unacceptable file type.';
logs.push(`[${isoNow()}] ❌ ${task.error}`);
logs.push(`[${isoNow()}] ❌❌❌❌❌ ${fullname}-${fileType} ${version ?? ''} ❌❌❌❌❌`);
await this.taskService.finishTask(task, TaskState.Fail, logs.join('\n'));
this.logger.info('[ProxyCacheService.executeTask:fail] taskId: %s, targetName: %s, %s',
task.taskId, task.targetName, task.error);
return;
}
} catch (error) {
task.error = error;
logs.push(`[${isoNow()}] ❌ ${task.error}`);
logs.push(`[${isoNow()}] ❌❌❌❌❌ ${fullname}-${fileType} ${version ?? ''} ❌❌❌❌❌`);
await this.taskService.finishTask(task, TaskState.Fail, logs.join('\n'));
this.logger.info('[ProxyCacheService.executeTask:fail] taskId: %s, targetName: %s, %s',
task.taskId, task.targetName, task.error);
return;
}
logs.push(`[${isoNow()}] 🟢 Update Success.`);
const isFullManifests = fileType === DIST_NAMES.FULL_MANIFESTS;
const cachedKey = await this.cacheService.getPackageEtag(fullname, isFullManifests);
if (cachedKey) {
const { shasum: etag } = await calculateIntegrity(cacheBytes);
await this.cacheService.savePackageEtagAndManifests(fullname, isFullManifests, etag, cacheBytes);
logs.push(`[${isoNow()}] 🟢 Update Cache Success.`);
}
await this.taskService.finishTask(task, TaskState.Success, logs.join('\n'));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

The executeTask method in ProxyCacheService logs errors adequately, as shown by the repeated logging statements for failures. However, it lacks logging for the start and successful completion of tasks, which are important for comprehensive debugging and monitoring of task execution. This should be addressed to fully meet the requirements mentioned in the review comment.

Analysis chain

The methods createTask, findExecuteTask, and executeTask are well-implemented. Ensure that the task execution method logs all steps and errors adequately for debugging purposes.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify comprehensive logging in task execution methods
rg --type ts "executeTask" -A 50 | grep "logger"

Length of output: 7658

Comment on lines +45 to +46
this.logger.error(err);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure error handling is robust. Consider adding more specific error information to help with debugging.

- this.logger.error(err);
+ this.logger.error('Error processing proxy cache update:', err);

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
this.logger.error(err);
}
this.logger.error('Error processing proxy cache update:', err);
}

Add unit tests to cover the error handling in the subscribe method.

Would you like me to help by adding some test cases for this scenario?

Comment on lines +26 to +54
async subscribe() {
if (this.config.cnpmcore.syncMode !== SyncMode.proxy) return;
if (executingCount >= this.config.cnpmcore.syncPackageWorkerMaxConcurrentTasks) return;

executingCount++;
try {
let task = await this.proxyCacheService.findExecuteTask();
while (task) {
const startTime = Date.now();
this.logger.info('[SyncProxyCacheWorker:subscribe:executeTask:start][%s] taskId: %s, targetName: %s, attempts: %s, params: %j, updatedAt: %s, delay %sms',
executingCount, task.taskId, task.targetName, task.attempts, task.data, task.updatedAt,
startTime - task.updatedAt.getTime());
await this.proxyCacheService.executeTask(task);
const use = Date.now() - startTime;
this.logger.info('[SyncProxyCacheWorker:subscribe:executeTask:success][%s] taskId: %s, targetName: %s, use %sms',
executingCount, task.taskId, task.targetName, use);
if (executingCount >= this.config.cnpmcore.syncPackageWorkerMaxConcurrentTasks) {
this.logger.info('[SyncProxyCacheWorker:subscribe:executeTask] current sync task count %s, exceed max concurrent tasks %s',
executingCount, this.config.cnpmcore.syncPackageWorkerMaxConcurrentTasks);
break;
}
// try next task
task = await this.proxyCacheService.findExecuteTask();
}
} catch (err) {
this.logger.error('[SyncProxyCacheWorker:subscribe:executeTask:error][%s] %s', executingCount, err);
} finally {
executingCount--;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the concurrency control with executingCount is thread-safe to avoid race conditions.

task = await this.proxyCacheService.findExecuteTask();
}
} catch (err) {
this.logger.error('[SyncProxyCacheWorker:subscribe:executeTask:error][%s] %s', executingCount, err);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add unit tests to cover the error handling in the subscribe method.

Would you like me to help by adding some test cases for this scenario?

Comment on lines +15 to +31
async saveProxyCache(proxyCacheEntity: ProxyCacheEntity) {
let model = proxyCacheEntity.version ?
await this.ProxyCache.findOne({ fullname: proxyCacheEntity.fullname, version: proxyCacheEntity.version, fileType: proxyCacheEntity.fileType }) :
await this.ProxyCache.findOne({ fullname: proxyCacheEntity.fullname, fileType: proxyCacheEntity.fileType });
if (model) {
model.updatedAt = proxyCacheEntity.updatedAt;
await model.save();
} else {
try {
model = await ModelConvertor.convertEntityToModel(proxyCacheEntity, this.ProxyCache);
} catch (e) {
e.message = '[ProxyCacheRepository] insert ProxyCache failed: ' + e.message;
throw e;
}
}
return model;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the saveProxyCache method's error handling is covered by unit tests, particularly the modification of the error message.

Would you like me to help by adding some test cases for this scenario?

Comment on lines 40 to 65
let { blockReason, manifest, pkg } = await this.packageManagerService.showPackageVersionManifest(scope, name, versionSpec, isSync, isFullManifests);
const fileType = isFullManifests ? DIST_NAMES.MANIFEST : DIST_NAMES.ABBREVIATED;
if (!pkg) {
const allowSync = this.getAllowSync(ctx);
throw this.createPackageNotFoundErrorWithRedirect(fullname, undefined, allowSync);
if (this.config.cnpmcore.syncMode === SyncMode.proxy) {
try {
manifest = await this.proxyCacheService.getPackageVersionManifest(fullname, fileType, versionSpec);
} catch (error) {
// 缓存manifest错误,创建刷新缓存任务
await this.proxyCacheService.createTask(`${fullname}/${fileType}`, { fullname, fileType });
throw error;
}
} else {
const allowSync = this.getAllowSync(ctx);
throw this.createPackageNotFoundErrorWithRedirect(fullname, undefined, allowSync);
}
}
if (blockReason) {
this.setCDNHeaders(ctx);
throw this.createPackageBlockError(blockReason, fullname, versionSpec);
}
if (!manifest) {
throw new NotFoundError(`${fullname}@${versionSpec} not found`);
if (this.config.cnpmcore.syncMode === SyncMode.proxy) {
manifest = await this.proxyCacheService.getPackageVersionManifest(fullname, fileType, versionSpec);
} else {
throw new NotFoundError(`${fullname}@${versionSpec} not found`);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add unit tests to cover the error handling and cache refresh task creation in the show method when in proxy mode.

Would you like me to help by adding some test cases for this scenario?

Comment on lines 73 to 93
if (this.config.cnpmcore.syncMode === SyncMode.proxy) {
// proxy mode
const fileType = isFullManifests ? DIST_NAMES.FULL_MANIFESTS : DIST_NAMES.ABBREVIATED_MANIFESTS;
let pkgManifest;
try {
pkgManifest = await this.proxyCacheService.getPackageManifest(fullname, fileType);
} catch (error) {
// 缓存manifest错误,创建刷新缓存任务
await this.proxyCacheService.createTask(`${fullname}/${fileType}`, { fullname, fileType });
throw error;
}
const nfsBytes = Buffer.from(JSON.stringify(pkgManifest));
const { shasum: etag } = await calculateIntegrity(nfsBytes);
result = { data: pkgManifest, etag, blockReason: '' };
} else {
result = await this.packageManagerService.listPackageAbbreviatedManifests(scope, name, isSync);
// sync mode
if (isFullManifests) {
result = await this.packageManagerService.listPackageFullManifests(scope, name, isSync);
} else {
result = await this.packageManagerService.listPackageAbbreviatedManifests(scope, name, isSync);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add unit tests to cover the error handling and cache refresh task creation in the show method when in proxy mode.

Would you like me to help by adding some test cases for this scenario?

@hezhengxu2018 hezhengxu2018 marked this pull request as draft April 17, 2024 14:51
@hezhengxu2018
Copy link
Collaborator Author

上游仓库可能会302返回一个重定向的地址,内网无法访问上游仓库返回的302地址。直接用egg-js的proxy插件不合适,这样的话proxy这个插件也有点不需要了

@hezhengxu2018
Copy link
Collaborator Author

好像之前想的有点问题,代理模式下可以不用管本地数据库的内容。依赖的manifest能从proxyCache读到就用缓存的,没有就返回上游的流,因为代理模式下只要创建的任务是指定版本的,本地的永远是不完整的,不用和其他模式的逻辑在一起。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants