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

Make it possible to start the first polling immediately #995

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/GH-994-start_immediately.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: GH-994 - Start Immediately
on:
push:
paths:
- '**GH-994**'
- 'action.yml'
- 'dist/**'
pull_request:
paths:
- '**GH-994**'
- 'action.yml'
- 'dist/**'

# Disable all permissions in workflow global as to setup clean room
# However PRs will have read permissions because this project is on a public repository
permissions: {}

jobs:
echo:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Print note
run: |
echo 'Triggered by ${{ github.event_name }} event'
wait:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- run: |
echo 'Triggered by ${{ github.event_name }} event'
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
- uses: ./
with:
wait-seconds-before-first-polling: 0 # This is the only value for testing in this file. So might be merged with other CIs
retry-method: 'equal_intervals'
min-interval-seconds: 5
attempt-limits: 30
skip-same-workflow: 'false'
wait-list: |
[
{
"workflowFile": "GH-994-start_immediately.yml",
"jobName": "echo",
"eventName": "${{ github.event_name }}"
}
]
15 changes: 15 additions & 0 deletions __tests__/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,22 @@ test('regex option does not have higher ReDoS possibilities', () => {
strictEqual(checkSync(yamlPattern.source, '').status, 'safe');
});

test('It can start immediately. GH-994', () => {
optionsEqual(
Options.parse({ ...defaultOptions, initialDuration: Temporal.Duration.from({ seconds: 0 }) }),
{
...defaultOptions,
initialDuration: Temporal.Duration.from({ seconds: 0 }),
},
);
});

test('Options reject invalid values', () => {
throws(() => Options.parse({ ...defaultOptions, initialDuration: Temporal.Duration.from({ seconds: -1 }) }), {
name: 'ZodError',
message: /Negative intervals are not reasonable for pollings/,
});

throws(() => Options.parse({ ...defaultOptions, leastInterval: Temporal.Duration.from({ seconds: 0 }) }), {
name: 'ZodError',
message: /Too short interval for pollings/,
Expand Down
10 changes: 5 additions & 5 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ inputs:
description: 'Github API URL'
required: true
default: '${{ github.api_url }}'
wait-seconds-before-first-polling:
wait-seconds-before-first-polling: # TODO: Since v4.x, Rename or fallback to `initial-duration` and make it possible to take ISO 8601 duration format. See GH-821
description: 'Wait this seconds before first polling'
required: false
default: '10'
min-interval-seconds:
default: '10' # TODO: Since v4.x, Consider setting shorter or 0 because using `equal_intervals` by default. Or respect the `min-interval-seconds` if this value is empty. See GH-596 and GH-994
min-interval-seconds: # TODO: Since v4.x, Rename or fallback to `least-interval` and make it possible to take ISO 8601 duration format. See GH-821
description: 'Wait this interval or the multiplied value (and jitter)'
required: false
default: '15'
early-exit:
description: 'Stop to rest pollings if faced at least 1 bad condition'
description: 'Stop rest pollings if faced at least 1 bad condition'
required: false
default: 'true'
retry-method:
description: 'How to wait for next polling'
required: false
default: 'equal_intervals'
default: 'equal_intervals' # Changed the default from `exponential_backoff` since GH-596(v2)
attempt-limits:
description: 'Stop rest pollings if reached to this limit'
required: false
Expand Down
22 changes: 15 additions & 7 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32565,12 +32565,18 @@ var MyDurationLike = z2.object({
nanoseconds: z2.number().optional()
}).strict().readonly();
var Durationable = z2.union([z2.string().duration(), MyDurationLike]).transform((item) => getDuration(item));
var Duration = z2.instanceof(mr.Duration).refine(
(d2) => mr.Duration.compare(d2, { seconds: 0 }) > 0,
var PositiveDuration = z2.instanceof(mr.Duration).refine(
(d2) => d2.sign > 0,
{
message: "Too short interval for pollings"
}
);
var ZeroableDuration = z2.instanceof(mr.Duration).refine(
(d2) => d2.sign >= 0,
{
message: "Negative intervals are not reasonable for pollings"
}
);
var defaultGrace = mr.Duration.from({ seconds: 10 });
function isDurationLike(my) {
for (const [_2, value] of Object.entries(my)) {
Expand All @@ -32582,7 +32588,7 @@ function isDurationLike(my) {
}
function getDuration(durationable) {
if (typeof durationable === "string" || isDurationLike(durationable)) {
return Duration.parse(mr.Duration.from(durationable));
return mr.Duration.from(durationable);
}
throw new Error("unexpected value is specified in durations");
}
Expand Down Expand Up @@ -32621,8 +32627,8 @@ var Options = z2.object({
apiUrl: z2.string().url(),
waitList: WaitList,
skipList: SkipList,
initialDuration: Duration,
leastInterval: Duration,
initialDuration: ZeroableDuration,
leastInterval: PositiveDuration,
retryMethod: retryMethods,
attemptLimits: z2.number().min(1),
isEarlyExit: z2.boolean(),
Expand Down Expand Up @@ -34161,8 +34167,10 @@ async function run() {
break;
}
if (attempts === 1) {
(0, import_core3.info)(`Wait ${readableDuration(options.initialDuration)} before first polling.`);
await wait(options.initialDuration);
if (options.initialDuration.sign > 0) {
(0, import_core3.info)(`Wait ${readableDuration(options.initialDuration)} before first polling.`);
await wait(options.initialDuration);
}
} else {
const interval = getInterval(options.retryMethod, options.leastInterval, attempts);
(0, import_core3.info)(`Wait ${readableDuration(interval)} before next polling to reduce API calls.`);
Expand Down
6 changes: 4 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ async function run(): Promise<void> {
}

if (attempts === 1) {
info(`Wait ${readableDuration(options.initialDuration)} before first polling.`);
await wait(options.initialDuration);
if (options.initialDuration.sign > 0) {
info(`Wait ${readableDuration(options.initialDuration)} before first polling.`);
await wait(options.initialDuration);
}
} else {
const interval = getInterval(options.retryMethod, options.leastInterval, attempts);
info(`Wait ${readableDuration(interval)} before next polling to reduce API calls.`);
Expand Down
19 changes: 12 additions & 7 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ type MyDurationLike = z.infer<typeof MyDurationLike>;
// IETF does not define duration formats in their RFCs, but in RFC 3399 refers ISO 8601 duration formats.
// https://www.ietf.org/rfc/rfc3339.txt
export const Durationable = z.union([z.string().duration(), MyDurationLike]).transform((item) => getDuration(item));
export const Duration = z.instanceof(Temporal.Duration).refine(
(d) => Temporal.Duration.compare(d, { seconds: 0 }) > 0,
export const PositiveDuration = z.instanceof(Temporal.Duration).refine(
(d) => d.sign > 0,
{
message: 'Too short interval for pollings',
},
);
type Duration = z.infer<typeof Duration>;
export const ZeroableDuration = z.instanceof(Temporal.Duration).refine(
(d) => d.sign >= 0,
{
message: 'Negative intervals are not reasonable for pollings',
},
);
const defaultGrace = Temporal.Duration.from({ seconds: 10 });

// workaround for https://github.com/colinhacks/zod/issues/635
Expand All @@ -58,9 +63,9 @@ function isDurationLike(my: MyDurationLike): my is DurationLike {
}

// workaround for https://github.com/colinhacks/zod/issues/635
export function getDuration(durationable: string | MyDurationLike): Duration {
export function getDuration(durationable: string | MyDurationLike): Temporal.Duration {
if (typeof durationable === 'string' || isDurationLike(durationable)) {
return Duration.parse(Temporal.Duration.from(durationable));
return Temporal.Duration.from(durationable);
}

throw new Error('unexpected value is specified in durations');
Expand Down Expand Up @@ -112,8 +117,8 @@ export const Options = z.object({
apiUrl: z.string().url(),
waitList: WaitList,
skipList: SkipList,
initialDuration: Duration,
leastInterval: Duration,
initialDuration: ZeroableDuration,
leastInterval: PositiveDuration,
retryMethod: retryMethods,
attemptLimits: z.number().min(1),
isEarlyExit: z.boolean(),
Expand Down
Loading