Giter Site home page Giter Site logo

captaincodeman / datastore-locker Goto Github PK

View Code? Open in Web Editor NEW
1.0 3.0 0.0 21 KB

A lock / lease mechanism to prevent duplicate execution of AppEngine tasks

License: Apache License 2.0

Go 100.00%
datastore-locker lock lease duplicate-tasks appengine-tasks appengine

datastore-locker's Introduction

Datastore Locker

Provides a lock / lease mechanism to prevent duplicate execution of appengine tasks and an easy way to continue long-running processes. Used by datastore-mapper.

Sometimes it's extremely difficult if not impossible to make tasks truly idempotent. e.g. if your task sends an email or charges a credit card then executing it twice could be 'a bad thing'. Actually preventing a task from running more than once can be challenging though ...

Although named tasks can be used to prevent duplicate tasks being enqueued they cannot be used together with datastore transactions which leaves you with the possibility that either the task scheduling or the datastore write could fail.

An unnamed task can be enqueued within a transaction to ensure that both happen or neither happen but that then leaves the possibility of that operation being repeated (if running in it's own task) which could cause duplicate tasks.

Whichever approach is used, the taskqueue only promises at-least-once delivery so there is also the chance that appengine will execute a task more than once.

The solution is to coordinate execution using information in the task with information in a datastore entity. This package aims to make it easy to restrict task execution by using a lease / lock mechanism.

By obtaining a lock on an entity within a datastore transaction we ensure that only a single instance of any task will be executed at once and, once processed, that duplicate execution will be prevented.

It can be used with single tasks or to chain a series of tasks in sequence with the sequence number used to prevent any old tasks being re-executed.

An exceptional situation can occur if a failure happens during processing of a task and the result cannot be communicated back to appengine (this is a platform issue). In this case the lock / lease is already held but the system cannot determine if the task completed or maybe it just failed to clear the lock. The locker will allow a timeout before querying the appengine logs to determine the task status. In the case of a complete failure with no log information, a timeout will prevent deadlock by overwriting the expired lock / lease.

Both overwritten locks and permanently failing tasks (past a configurable number of retries) can be alerted by email as needing further investigation.

Usage

See the example project for a simple demonstration of locker being used.

Embed the locker.Lock field within the struct you want lock on.

Foo struct {
    locker.Struct
    Value string `datastore:"value"`  
}

Create an instance of the locker and configure as required:

l := locker.NewLocker()

l := locker.NewLocker(
  locker.LeaseDuration(time.Duration(5)*time.Minute),
  locker.LeaseTimeout(time.Duration(15)*time.Minute),
  locker.AlertOnOverwrite,
)

l := locker.NewLocker(locker.LogVerbose)

Schedule a task to be executed once:

key := datastore.NewKey(c, "foo", "", 1, nil)
entity := &Foo{Value:"bar"}
err := l.Schedule(c, key, entity, "/task/handler/url", nil)
if err != nil {
  // operation failed (entity not saved and task not enqueued)
}

Handle the task execution:

func init() {
  http.Handle("/task/handler/url", locker.Handle(fooHandler, fooFactory)
}

// the task handler needs a factory to construct an instance of our entity
func fooFactory() interface{} {
  return new(Foo)
}

// the handler for the task will be passed the appengine context, request, datastore key and entity
func foohandler(c context.Context, r *http.Request, key *datastore.Key, entity locker.Lockable) error {
  foo := entity.(*Foo)

  switch foo.Sequence {
    case 1:
      // step 1 processing, e.g. charge credit card
      // schedule another task to follow this one:
      return l.Schedule(c, key, entity, "/task/handler/url", nil)
    case 2:
      // step 2 processing, e.g. send confirmation email
      // mark the task as completed (to prevent the last task re-executing)
      return l.Complete(c, key, entity)
  }

  // returning an error would cause the task to be failed and retried (normal task semantics)
  // a configurable number of retries can be set to prevent endless attempts from happening
  return nil
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.