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

new script #20

Open
hbrooks opened this issue Jan 23, 2025 · 2 comments · May be fixed by #21
Open

new script #20

hbrooks opened this issue Jan 23, 2025 · 2 comments · May be fixed by #21

Comments

@hbrooks
Copy link
Member

hbrooks commented Jan 23, 2025

I need help creating a runnable script that showcases a feature within a workflow orchestration system called Hatchet.

My goal is to replace every hardcoded TypeScript code snippet in the Hatchet documentation with a reference to an example script in a public GitHub repository. I need your help, @ellipsis-dev, creating those example scripts.

Please create a script in the src/examples directory of this repo (or a subdirectory, if you choose to create one). This script needs to clearly showcase the feature outlined in the below docs file. You need to:

  1. Create an independently executable script. We want everyone to be able to really simply try out all the awesome features within Hatchet, so make it simple and containing as little code as possible to communicate the feature. To see how other examples files look, see the src/examples directory.

  2. Within the script, enclose the most important code with // ❓ {NAME} before and // ‼️ after. The code between these two comments will be what is shown in the docs - so this section in particular should look very similar to what the actual code snippet looks like. The {NAME} should be replaced with a reasonable name (camelCase) of the example. We'll use this name later on when removing the code snippets

  3. Update package.json to run your new example script! Be sure to follow the format used by the rest of the example: commands.

Here's an example of a well formed script:

import Hatchet from '../sdk';
import { Workflow } from '../workflow';

const hatchet = Hatchet.init();

// ❓ OnFailure Step
// This workflow will fail because the step will throw an error
// we define an onFailure step to handle this case

const workflow: Workflow = {
  // ... normal workflow definition
  id: 'on-failure-example',
  description: 'test',
  on: {
    event: 'user:create',
  },
  // ,
  steps: [
    {
      name: 'step1',
      run: async (ctx) => {
        // 👀 this step will always throw an error
        throw new Error('Step 1 failed');
      },
    },
  ],
  // 👀 After the workflow fails, this special step will run
  onFailure: {
    name: 'on-failure-step',
    run: async (ctx) => {
      // 👀 we can do things like perform cleanup logic
      // or notify a user here

      // 👀 you can access the error from the failed step(s) like this
      console.log(ctx.stepRunErrors());

      return { onFailure: 'step' };
    },
  },
};
// ‼️

// ❓ OnFailure With Details
// Coming soon to TypeScript! https://github.com/hatchet-dev/hatchet-typescript/issues/447
// ‼️

async function main() {
  const worker = await hatchet.worker('example-worker', 1);
  await worker.registerWorkflow(workflow);
  worker.start();
}

main();

This example contains two TypeScript code snippets that are referenced in the docs. Only 1 is implemented, but you get the idea.

Later, I'll go through the docs script and replace the hardcoded TypeScript code snippets with a reference to your new script.

Okay, ready to create a new executable example script? Let's do it! 🚀 Here is the docs file we're currently updating, it's file path is frontend/docs/pages/home/features/timeouts.mdx and the contents of the file are:


import { Callout, Card, Cards, Steps, Tabs } from "nextra/components";
import UniversalTabs from "../../../components/UniversalTabs";

Timeouts in Hatchet

Timeouts are an important concept in Hatchet that allow you to control how long a workflow or step is allowed to run before it is considered to have failed. This is useful for ensuring that your workflows don't run indefinitely and consume unnecessary resources. Timeouts in Hatchat are treated as failures and the step will be retried if specified.

There are two types of timeouts in Hatchet:

  1. Scheduling Timeouts (Default 5m) - the time a step is allowed to wait in the queue before it is cancelled
  2. Execution Timeouts (Default 60s) - the time a step is allowed to run before it is considered to have failed

Timeout Format

In Hatchet, timeouts are specified using a string in the format <number><unit>, where <number> is an integer and <unit> is one of:

  • s for seconds
  • m for minutes
  • h for hours
  • d for days

For example:

  • 10s means 10 seconds
  • 4m means 4 minutes
  • 1h means 1 hour
  • 2d means 2 days

If no unit is specified, seconds are assumed.

Scheduling Timeouts

To specify a timeout for an entire workflow, you can set the Schedule Timeout property in the workflow definition:

<UniversalTabs items={['Python', 'Typescript', 'Go']}>
<Tabs.Tab>

@hatchet.workflow(schedule_timeout="2m")
class TimeoutWorkflow:
  # ...

</Tabs.Tab>
<Tabs.Tab>

const myWorkflow: Workflow = {
  id: "my-workflow",
  // ...
  scheduleTimeout: "2m",
  // ...
};

</Tabs.Tab>
<Tabs.Tab>

cleanup, err := run(events, worker.WorkflowJob{
  Name: "timeout",
  Description: "timeout",
  ScheduleTimeout: "2m",
  Steps: []*worker.WorkflowStep{
    worker.Fn(func(ctx worker.HatchetContext) (result *stepOneOutput, err error) {
      time.Sleep(time.Second * 60)
      return nil, nil
    }).SetName("step-one")
  },
})

</Tabs.Tab>

This would set a timeout of 2 minutes for all steps in the workflow. If the workflow takes longer than 2 minutes for assignment of the step to a worker, it will be cancelled and will not be assigned to a worker.

Step Timeouts

To specify a timeout for an individual step, you can set the timeout property in the step definition:

<UniversalTabs items={['Python', 'Typescript', 'Go']}>
<Tabs.Tab>

@hatchet.step(timeout="30s")
def timeout(self, context):
    try:
        print("started step2")
        time.sleep(5)
        print("finished step2")
    except Exception as e:
        print("caught an exception: " + str(e))
        raise e

</Tabs.Tab>
<Tabs.Tab>

const myStep: CreateStep<any, any> = {
  name: "my-step",
  // ...
  timeout: "30s",
  run: async (ctx) => {
    // ...
  },
};

</Tabs.Tab>
<Tabs.Tab>

worker.WorkflowJob{
  Name: "timeout",
  Description: "timeout",
  Steps: []*worker.WorkflowStep{
    worker.Fn(func(ctx worker.HatchetContext) (result *stepOneOutput, err error) {
      time.Sleep(time.Second * 60)
      return nil, nil
    }).SetName("step-one").SetTimeout("30s"),
  },
}

</Tabs.Tab>

This would set a timeout of 30 seconds for this specific step. If the step takes longer than 30 seconds to complete, it will fail and the workflow will be cancelled.

A timed out step does not guarantee that the step will be stopped immediately. The step will be stopped as soon as the worker is able to stop the step. See [cancellation](/features/cancellation) for more information.

Refreshing Timeouts

In some cases, you may need to extend the timeout for a step while it is running. This can be done using the refreshTimeout function provided by the step context (ctx).

For example:

<UniversalTabs items={['Python', 'Typescript', 'Go']}>
<Tabs.Tab>

@hatchet.step(timeout="30s")
def timeout(self, context):
    time.sleep(20)
    context.refresh_timeout("15s")
    time.sleep(10)
    return {
      step1: "step1 results!"
    }

</Tabs.Tab>
<Tabs.Tab>

const myStep: CreateStep<any, any> = {
  name: "my-step",
  timeout: "30s",
  run: async (ctx) => {
    await sleep(20 * 1000);
    ctx.refreshTimeout("15s");
    await sleep(10 * 1000);
    return { step1: "step1 results!" };
  },
};

</Tabs.Tab>
<Tabs.Tab>

worker.WorkflowJob{
		Name: "timeout",
		Description: "timeout",
		Steps: []*worker.WorkflowStep{
			worker.Fn(func(ctx worker.HatchetContext) (result *stepOneOutput, err error) {
				time.Sleep(time.Second * 20)
				ctx.RefreshTimeout("15s")
        time.Sleep(time.Second * 10)
        return return &stepOneOutput{
					Message: "step1 results!",
				}, nil
			}).SetName("step-one").SetTimeout("30s"),
    },
}

</Tabs.Tab>

In this example, the step initially has a timeout of 30 seconds. After 19 seconds, the refreshTimeout function is called with an argument of '15s', which extends the timeout by an additional 15 seconds. This allows the step to continue running for a total of 45 seconds (30 seconds initial timeout + 15 seconds refreshed timeout).

The refreshTimeout function can be called multiple times within a step to further extend the timeout as needed.

Use Cases

Timeouts are useful in a variety of scenarios:

  • Ensuring workflows don't run indefinitely and consume unnecessary resources
  • Failing workflows early if a critical step takes too long
  • Keeping workflows responsive by ensuring individual steps complete in a timely manner
  • Preventing infinite loops or hung processes from blocking the entire system

For example, if you have a workflow that makes an external API call, you may want to set a timeout to ensure the workflow fails quickly if the API is unresponsive, rather than waiting indefinitely.

By carefully considering timeouts for your workflows and steps, you can build more resilient and responsive systems with Hatchet.

@hbrooks
Copy link
Member Author

hbrooks commented Jan 23, 2025

@ellipsis-dev create a PR

Copy link

ellipsis-dev bot commented Jan 23, 2025

Pull request created: #21

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 a pull request may close this issue.

1 participant