[
  {
    "path": ".deepsource.toml",
    "content": "version = 1\n\ntest_patterns = [\"**/*_test.go\"]\n\nexclude_patterns = [\"example/**\"]\n\n[[analyzers]]\nname = \"go\"\nenabled = true\n\n  [analyzers.meta]\n  import_paths = [\"github.com/RichardKnop/machinery\"]"
  },
  {
    "path": ".gitignore",
    "content": "coverage*\n_vendor-*\n.idea/\n.env\n.DS_Store\ndump.rdb\n"
  },
  {
    "path": ".travis.yml",
    "content": "---\nlanguage: go\n\ngo:\n  - 1.13.x\n\nenv:\n  - GO111MODULE=on\n\nservices:\n  - docker\n\nscript:\n  - make ci\n\nafter_success:\n  - bash <(curl -s https://codecov.io/bash)\n"
  },
  {
    "path": "Dockerfile.gcppubsub",
    "content": "FROM google/cloud-sdk:530.0.0-alpine\n\nRUN apk --update add openjdk8-jre\nRUN gcloud components install --quiet beta pubsub-emulator\nRUN mkdir -p /var/pubsub\n\nEXPOSE 8085\n\nCMD [ \"gcloud\", \"beta\", \"emulators\", \"pubsub\", \"start\", \"--host-port=0.0.0.0:8085\"]\n"
  },
  {
    "path": "Dockerfile.test",
    "content": "# Start from a Debian image with the latest version of Go installed\n# and a workspace (GOPATH) configured at /go.\nFROM golang\n\n# Set environment variables\nENV PATH /go/bin:$PATH\n\n# Cd into the source code directory\nWORKDIR /go/src/github.com/RichardKnop/machinery\n\n# Copy the local package files to the container's workspace.\nADD . /go/src/github.com/RichardKnop/machinery\n\n# Run integration tests as default command\nCMD /go/src/github.com/RichardKnop/machinery/wait-for-it.sh rabbitmq:5672 -- make test-with-coverage\n"
  },
  {
    "path": "LICENSE",
    "content": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. “Contributor”\n\n     means each individual or legal entity that creates, contributes to the\n     creation of, or owns Covered Software.\n\n1.2. “Contributor Version”\n\n     means the combination of the Contributions of others (if any) used by a\n     Contributor and that particular Contributor’s Contribution.\n\n1.3. “Contribution”\n\n     means Covered Software of a particular Contributor.\n\n1.4. “Covered Software”\n\n     means Source Code Form to which the initial Contributor has attached the\n     notice in Exhibit A, the Executable Form of such Source Code Form, and\n     Modifications of such Source Code Form, in each case including portions\n     thereof.\n\n1.5. “Incompatible With Secondary Licenses”\n     means\n\n     a. that the initial Contributor has attached the notice described in\n        Exhibit B to the Covered Software; or\n\n     b. that the Covered Software was made available under the terms of version\n        1.1 or earlier of the License, but not also under the terms of a\n        Secondary License.\n\n1.6. “Executable Form”\n\n     means any form of the work other than Source Code Form.\n\n1.7. “Larger Work”\n\n     means a work that combines Covered Software with other material, in a separate\n     file or files, that is not Covered Software.\n\n1.8. “License”\n\n     means this document.\n\n1.9. “Licensable”\n\n     means having the right to grant, to the maximum extent possible, whether at the\n     time of the initial grant or subsequently, any and all of the rights conveyed by\n     this License.\n\n1.10. “Modifications”\n\n     means any of the following:\n\n     a. any file in Source Code Form that results from an addition to, deletion\n        from, or modification of the contents of Covered Software; or\n\n     b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. “Patent Claims” of a Contributor\n\n      means any patent claim(s), including without limitation, method, process,\n      and apparatus claims, in any patent Licensable by such Contributor that\n      would be infringed, but for the grant of the License, by the making,\n      using, selling, offering for sale, having made, import, or transfer of\n      either its Contributions or its Contributor Version.\n\n1.12. “Secondary License”\n\n      means either the GNU General Public License, Version 2.0, the GNU Lesser\n      General Public License, Version 2.1, the GNU Affero General Public\n      License, Version 3.0, or any later versions of those licenses.\n\n1.13. “Source Code Form”\n\n      means the form of the work preferred for making modifications.\n\n1.14. “You” (or “Your”)\n\n      means an individual or a legal entity exercising rights under this\n      License. For legal entities, “You” includes any entity that controls, is\n      controlled by, or is under common control with You. For purposes of this\n      definition, “control” means (a) the power, direct or indirect, to cause\n      the direction or management of such entity, whether by contract or\n      otherwise, or (b) ownership of more than fifty percent (50%) of the\n      outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n     Each Contributor hereby grants You a world-wide, royalty-free,\n     non-exclusive license:\n\n     a. under intellectual property rights (other than patent or trademark)\n        Licensable by such Contributor to use, reproduce, make available,\n        modify, display, perform, distribute, and otherwise exploit its\n        Contributions, either on an unmodified basis, with Modifications, or as\n        part of a Larger Work; and\n\n     b. under Patent Claims of such Contributor to make, use, sell, offer for\n        sale, have made, import, and otherwise transfer either its Contributions\n        or its Contributor Version.\n\n2.2. Effective Date\n\n     The licenses granted in Section 2.1 with respect to any Contribution become\n     effective for each Contribution on the date the Contributor first distributes\n     such Contribution.\n\n2.3. Limitations on Grant Scope\n\n     The licenses granted in this Section 2 are the only rights granted under this\n     License. No additional rights or licenses will be implied from the distribution\n     or licensing of Covered Software under this License. Notwithstanding Section\n     2.1(b) above, no patent license is granted by a Contributor:\n\n     a. for any code that a Contributor has removed from Covered Software; or\n\n     b. for infringements caused by: (i) Your and any other third party’s\n        modifications of Covered Software, or (ii) the combination of its\n        Contributions with other software (except as part of its Contributor\n        Version); or\n\n     c. under Patent Claims infringed by Covered Software in the absence of its\n        Contributions.\n\n     This License does not grant any rights in the trademarks, service marks, or\n     logos of any Contributor (except as may be necessary to comply with the\n     notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n     No Contributor makes additional grants as a result of Your choice to\n     distribute the Covered Software under a subsequent version of this License\n     (see Section 10.2) or under the terms of a Secondary License (if permitted\n     under the terms of Section 3.3).\n\n2.5. Representation\n\n     Each Contributor represents that the Contributor believes its Contributions\n     are its original creation(s) or it has sufficient rights to grant the\n     rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n     This License is not intended to limit any rights You have under applicable\n     copyright doctrines of fair use, fair dealing, or other equivalents.\n\n2.7. Conditions\n\n     Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n     Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n     All distribution of Covered Software in Source Code Form, including any\n     Modifications that You create or to which You contribute, must be under the\n     terms of this License. You must inform recipients that the Source Code Form\n     of the Covered Software is governed by the terms of this License, and how\n     they can obtain a copy of this License. You may not attempt to alter or\n     restrict the recipients’ rights in the Source Code Form.\n\n3.2. Distribution of Executable Form\n\n     If You distribute Covered Software in Executable Form then:\n\n     a. such Covered Software must also be made available in Source Code Form,\n        as described in Section 3.1, and You must inform recipients of the\n        Executable Form how they can obtain a copy of such Source Code Form by\n        reasonable means in a timely manner, at a charge no more than the cost\n        of distribution to the recipient; and\n\n     b. You may distribute such Executable Form under the terms of this License,\n        or sublicense it under different terms, provided that the license for\n        the Executable Form does not attempt to limit or alter the recipients’\n        rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n     You may create and distribute a Larger Work under terms of Your choice,\n     provided that You also comply with the requirements of this License for the\n     Covered Software. If the Larger Work is a combination of Covered Software\n     with a work governed by one or more Secondary Licenses, and the Covered\n     Software is not Incompatible With Secondary Licenses, this License permits\n     You to additionally distribute such Covered Software under the terms of\n     such Secondary License(s), so that the recipient of the Larger Work may, at\n     their option, further distribute the Covered Software under the terms of\n     either this License or such Secondary License(s).\n\n3.4. Notices\n\n     You may not remove or alter the substance of any license notices (including\n     copyright notices, patent notices, disclaimers of warranty, or limitations\n     of liability) contained within the Source Code Form of the Covered\n     Software, except that You may alter any license notices to the extent\n     required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n     You may choose to offer, and to charge a fee for, warranty, support,\n     indemnity or liability obligations to one or more recipients of Covered\n     Software. However, You may do so only on Your own behalf, and not on behalf\n     of any Contributor. You must make it absolutely clear that any such\n     warranty, support, indemnity, or liability obligation is offered by You\n     alone, and You hereby agree to indemnify every Contributor for any\n     liability incurred by such Contributor as a result of warranty, support,\n     indemnity or liability terms You offer. You may include additional\n     disclaimers of warranty and limitations of liability specific to any\n     jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n   If it is impossible for You to comply with any of the terms of this License\n   with respect to some or all of the Covered Software due to statute, judicial\n   order, or regulation then You must: (a) comply with the terms of this License\n   to the maximum extent possible; and (b) describe the limitations and the code\n   they affect. Such description must be placed in a text file included with all\n   distributions of the Covered Software under this License. Except to the\n   extent prohibited by statute or regulation, such description must be\n   sufficiently detailed for a recipient of ordinary skill to be able to\n   understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n     fail to comply with any of its terms. However, if You become compliant,\n     then the rights granted under this License from a particular Contributor\n     are reinstated (a) provisionally, unless and until such Contributor\n     explicitly and finally terminates Your grants, and (b) on an ongoing basis,\n     if such Contributor fails to notify You of the non-compliance by some\n     reasonable means prior to 60 days after You have come back into compliance.\n     Moreover, Your grants from a particular Contributor are reinstated on an\n     ongoing basis if such Contributor notifies You of the non-compliance by\n     some reasonable means, this is the first time You have received notice of\n     non-compliance with this License from such Contributor, and You become\n     compliant prior to 30 days after Your receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n     infringement claim (excluding declaratory judgment actions, counter-claims,\n     and cross-claims) alleging that a Contributor Version directly or\n     indirectly infringes any patent, then the rights granted to You by any and\n     all Contributors for the Covered Software under Section 2.1 of this License\n     shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n     license agreements (excluding distributors and resellers) which have been\n     validly granted by You or Your distributors under this License prior to\n     termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n   Covered Software is provided under this License on an “as is” basis, without\n   warranty of any kind, either expressed, implied, or statutory, including,\n   without limitation, warranties that the Covered Software is free of defects,\n   merchantable, fit for a particular purpose or non-infringing. The entire\n   risk as to the quality and performance of the Covered Software is with You.\n   Should any Covered Software prove defective in any respect, You (not any\n   Contributor) assume the cost of any necessary servicing, repair, or\n   correction. This disclaimer of warranty constitutes an essential part of this\n   License. No use of  any Covered Software is authorized under this License\n   except under this disclaimer.\n\n7. Limitation of Liability\n\n   Under no circumstances and under no legal theory, whether tort (including\n   negligence), contract, or otherwise, shall any Contributor, or anyone who\n   distributes Covered Software as permitted above, be liable to You for any\n   direct, indirect, special, incidental, or consequential damages of any\n   character including, without limitation, damages for lost profits, loss of\n   goodwill, work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses, even if such party shall have been\n   informed of the possibility of such damages. This limitation of liability\n   shall not apply to liability for death or personal injury resulting from such\n   party’s negligence to the extent applicable law prohibits such limitation.\n   Some jurisdictions do not allow the exclusion or limitation of incidental or\n   consequential damages, so this exclusion and limitation may not apply to You.\n\n8. Litigation\n\n   Any litigation relating to this License may be brought only in the courts of\n   a jurisdiction where the defendant maintains its principal place of business\n   and such litigation shall be governed by laws of that jurisdiction, without\n   reference to its conflict-of-law provisions. Nothing in this Section shall\n   prevent a party’s ability to bring cross-claims or counter-claims.\n\n9. Miscellaneous\n\n   This License represents the complete agreement concerning the subject matter\n   hereof. If any provision of this License is held to be unenforceable, such\n   provision shall be reformed only to the extent necessary to make it\n   enforceable. Any law or regulation which provides that the language of a\n   contract shall be construed against the drafter shall not be used to construe\n   this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n      Mozilla Foundation is the license steward. Except as provided in Section\n      10.3, no one other than the license steward has the right to modify or\n      publish new versions of this License. Each version will be given a\n      distinguishing version number.\n\n10.2. Effect of New Versions\n\n      You may distribute the Covered Software under the terms of the version of\n      the License under which You originally received the Covered Software, or\n      under the terms of any subsequent version published by the license\n      steward.\n\n10.3. Modified Versions\n\n      If you create software not governed by this License, and you want to\n      create a new license for such software, you may create and use a modified\n      version of this License if you rename the license and remove any\n      references to the name of the license steward (except to note that such\n      modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n      If You choose to distribute Source Code Form that is Incompatible With\n      Secondary Licenses under the terms of this version of the License, the\n      notice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n      This Source Code Form is subject to the\n      terms of the Mozilla Public License, v.\n      2.0. If a copy of the MPL was not\n      distributed with this file, You can\n      obtain one at\n      http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file, then\nYou may include the notice in a location (such as a LICENSE file in a relevant\ndirectory) where a recipient would be likely to look for such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - “Incompatible With Secondary Licenses” Notice\n\n      This Source Code Form is “Incompatible\n      With Secondary Licenses”, as defined by\n      the Mozilla Public License, v. 2.0.\n\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: fmt lint golint test test-with-coverage ci\n# TODO: When Go 1.9 is released vendor folder should be ignored automatically\nPACKAGES=`go list ./... | grep -v vendor | grep -v mocks`\n\nfmt:\n\tfor pkg in ${PACKAGES}; do \\\n\t\tgo fmt $$pkg; \\\n\tdone;\n\nlint:\n\tgometalinter --tests --disable-all --deadline=120s -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode ./...\n\ngolint:\n\tfor pkg in ${PACKAGES}; do \\\n\t\tgolint -set_exit_status $$pkg || GOLINT_FAILED=1; \\\n\tdone; \\\n\t[ -z \"$$GOLINT_FAILED\" ]\n\ntest:\n\tTEST_FAILED= ; \\\n\tfor pkg in ${PACKAGES}; do \\\n\t\tgo test $$pkg || TEST_FAILED=1; \\\n\tdone; \\\n\t[ -z \"$$TEST_FAILED\" ]\n\ntest-with-coverage:\n\techo \"\" > coverage.out\n\techo \"mode: set\" > coverage-all.out\n\tTEST_FAILED= ; \\\n\tfor pkg in ${PACKAGES}; do \\\n\t\tgo test -coverprofile=coverage.out -covermode=set $$pkg || TEST_FAILED=1; \\\n\t\ttail -n +2 coverage.out >> coverage-all.out; \\\n\tdone; \\\n\t[ -z \"$$TEST_FAILED\" ]\n\t#go tool cover -html=coverage-all.out\n\nci:\n\tbash -c 'docker-compose -f docker-compose.test.yml -p machinery_ci up --build --abort-on-container-exit --exit-code-from sut'\n"
  },
  {
    "path": "README.md",
    "content": "[1]: https://raw.githubusercontent.com/RichardKnop/assets/master/machinery/example_worker.png\n[2]: https://raw.githubusercontent.com/RichardKnop/assets/master/machinery/example_worker_receives_tasks.png\n[3]: http://patreon_public_assets.s3.amazonaws.com/sized/becomeAPatronBanner.png\n\n## Machinery\n\nMachinery is an asynchronous task queue/job queue based on distributed message passing.\n\n[![godoc for RichardKnop/machinery](https://godoc.org/github.com/nathany/looper?status.svg)](http://godoc.org/github.com/RichardKnop/machinery/v1)\n[![codecov for RichardKnop/machinery](https://codecov.io/gh/RichardKnop/machinery/branch/master/graph/badge.svg)](https://codecov.io/gh/RichardKnop/machinery)\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/RichardKnop/machinery)](https://goreportcard.com/report/github.com/RichardKnop/machinery)\n[![OpenTracing Badge](https://img.shields.io/badge/OpenTracing-enabled-blue.svg)](http://opentracing.io)\n\n[![Sourcegraph for RichardKnop/machinery](https://sourcegraph.com/github.com/RichardKnop/machinery/-/badge.svg)](https://sourcegraph.com/github.com/RichardKnop/machinery?badge)\n[![Donate Bitcoin](https://img.shields.io/badge/donate-bitcoin-orange.svg)](https://richardknop.github.io/donate/)\n\n---\n\n* [V2 Experiment](#v2-experiment)\n* [First Steps](#first-steps)\n* [Configuration](#configuration)\n  * [Lock](#lock)\n  * [Broker](#broker)\n  * [DefaultQueue](#defaultqueue)\n  * [ResultBackend](#resultbackend)\n  * [ResultsExpireIn](#resultsexpirein)\n  * [AMQP](#amqp-2)\n  * [DynamoDB](#dynamodb)\n  * [Redis](#redis-2)\n  * [GCPPubSub](#gcppubsub)\n* [Custom Logger](#custom-logger)\n* [Server](#server)\n* [Workers](#workers)\n* [Tasks](#tasks)\n  * [Registering Tasks](#registering-tasks)\n  * [Signatures](#signatures)\n  * [Supported Types](#supported-types)\n  * [Sending Tasks](#sending-tasks)\n  * [Delayed Tasks](#delayed-tasks)\n  * [Retry Tasks](#retry-tasks)\n  * [Get Pending Tasks](#get-pending-tasks)\n  * [Keeping Results](#keeping-results)\n* [Workflows](#workflows)\n  * [Groups](#groups)\n  * [Chords](#chords)\n  * [Chains](#chains)\n* [Periodic Tasks & Workflows](#periodic-tasks--workflows)\n  * [Periodic Tasks](#periodic-tasks)\n  * [Periodic Groups](#periodic-groups)\n  * [Periodic Chains](#periodic-chains)\n  * [Periodic Chords](#periodic-chords)\n* [Development](#development)\n  * [Requirements](#requirements)\n  * [Dependencies](#dependencies)\n  * [Testing](#testing)\n\n### V2\n\nI recommend using V2 in order to avoid having to import all dependencies for brokers and backends you are not using.\n\nInstead of factory, you will need to inject broker and backend objects to the server constructor:\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v2\"\n  backendsiface \"github.com/RichardKnop/machinery/v2/backends/iface\"\n  brokersiface \"github.com/RichardKnop/machinery/v2/brokers/iface\"\n  locksiface \"github.com/RichardKnop/machinery/v2/locks/iface\"\n)\n\nvar broker brokersiface.Broker\nvar backend backendsiface.Backend\nvar lock locksiface.Lock\nserver := machinery.NewServer(cnf, broker, backend, lock)\n// server.NewWorker(\"machinery\", 10)\n```\n\n### First Steps\n\nTo install recommended v2 release:\n\n```sh\ngo get github.com/RichardKnop/machinery/v2\n```\n\nIf you want to use legacy v1 version, you still can:\n\n```sh\ngo get github.com/RichardKnop/machinery\n```\n\nFirst, you will need to define some tasks. Look at sample tasks in `v2/example/tasks/tasks.go` to see a few examples.\n\nSecond, you will need to launch a worker process with one of these commands (v2 is recommended since it doesn't import dependencies for all brokers / backends, only those you actually need):\n\n```sh\ncd v2/\ngo run example/amqp/main.go worker\ngo run example/redigo/main.go worker // Redis with redigo driver\ngo run example/go-redis/main.go worker // Redis with Go Redis driver\n\ngo run example/amqp/main.go worker\ngo run example/redis/main.go worker\n```\n\n![Example worker][1]\n\nFinally, once you have a worker running and waiting for tasks to consume, send some tasks with one of these commands (v2 is recommended since it doesn't import dependencies for all brokers / backends, only those you actually need):\n\n```sh\ncd v2\ngo run v2/example/amqp/main.go send\ngo run v2/example/redigo/main.go send // Redis with redigo driver\ngo run v2/example/go-redis/main.go send // Redis with Go Redis driver\n```\n\nYou will be able to see the tasks being processed asynchronously by the worker:\n\n![Example worker receives tasks][2]\n\n### Configuration\n\nThe [config](/v2/config/config.go) package has convenience methods for loading configuration from environment variables or a YAML file. For example, load configuration from environment variables:\n\n```go\ncnf, err := config.NewFromEnvironment()\n```\n\nOr load from YAML file:\n\n```go\ncnf, err := config.NewFromYaml(\"config.yml\", true)\n```\n\nSecond boolean flag enables live reloading of configuration every 10 seconds. Use `false` to disable live reloading.\n\nMachinery configuration is encapsulated by a `Config` struct and injected as a dependency to objects that need it.\n\n#### Lock\n\n##### Redis\n\nUse Redis URL in one of these formats:\n\n```\nredis://[password@]host[port][/db_num]\n```\n\nFor example:\n\n1. `redis://localhost:6379`, or with password `redis://password@localhost:6379`\n\n#### Broker\n\nA message broker. Currently supported brokers are:\n\n##### AMQP\n\nUse AMQP URL in the format:\n\n```\namqp://[username:password@]@host[:port]\n```\n\nFor example:\n\n1. `amqp://guest:guest@localhost:5672`\n\nAMQP also supports multiples brokers urls. You need to specify the URL separator in the `MultipleBrokerSeparator` field.\n\n##### Redis\n\nUse Redis URL in one of these formats:\n\n```\nredis://[password@]host[port][/db_num]\nredis+socket://[password@]/path/to/file.sock[:/db_num]\n```\n\nFor example:\n\n1. `redis://localhost:6379`, or with password `redis://password@localhost:6379`\n2. `redis+socket://password@/path/to/file.sock:/0`\n\n##### AWS SQS\n\nUse AWS SQS URL in the format:\n\n```\nhttps://sqs.us-east-2.amazonaws.com/123456789012\n```\n\nSee [AWS SQS docs](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html) for more information.\nAlso, configuring `AWS_REGION` is required, or an error would be thrown.\n\nTo use a manually configured SQS Client:\n\n```go\nvar sqsClient = sqs.New(session.Must(session.NewSession(&aws.Config{\n  Region:         aws.String(\"YOUR_AWS_REGION\"),\n  Credentials:    credentials.NewStaticCredentials(\"YOUR_AWS_ACCESS_KEY\", \"YOUR_AWS_ACCESS_SECRET\", \"\"),\n  HTTPClient:     &http.Client{\n    Timeout: time.Second * 120,\n  },\n})))\nvar visibilityTimeout = 20\nvar cnf = &config.Config{\n  Broker:          \"YOUR_SQS_URL\"\n  DefaultQueue:    \"machinery_tasks\",\n  ResultBackend:   \"YOUR_BACKEND_URL\",\n  SQS: &config.SQSConfig{\n    Client: sqsClient,\n    // if VisibilityTimeout is nil default to the overall visibility timeout setting for the queue\n    // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html\n    VisibilityTimeout: &visibilityTimeout,\n    WaitTimeSeconds: 30,\n  },\n}\n```\n\n##### GCP Pub/Sub\n\nUse GCP Pub/Sub URL in the format:\n\n```\ngcppubsub://YOUR_GCP_PROJECT_ID/YOUR_PUBSUB_SUBSCRIPTION_NAME\n```\n\nTo use a manually configured Pub/Sub Client:\n\n```go\npubsubClient, err := pubsub.NewClient(\n    context.Background(),\n    \"YOUR_GCP_PROJECT_ID\",\n    option.WithServiceAccountFile(\"YOUR_GCP_SERVICE_ACCOUNT_FILE\"),\n)\n\ncnf := &config.Config{\n  Broker:          \"gcppubsub://YOUR_GCP_PROJECT_ID/YOUR_PUBSUB_SUBSCRIPTION_NAME\"\n  DefaultQueue:    \"YOUR_PUBSUB_TOPIC_NAME\",\n  ResultBackend:   \"YOUR_BACKEND_URL\",\n  GCPPubSub: config.GCPPubSubConfig{\n    Client: pubsubClient,\n  },\n}\n```\n\n#### DefaultQueue\n\nDefault queue name, e.g. `machinery_tasks`.\n\n#### ResultBackend\n\nResult backend to use for keeping task states and results.\n\nCurrently supported backends are:\n\n##### Redis\n\nUse Redis URL in one of these formats:\n\n```\nredis://[password@]host[port][/db_num]\nredis+socket://[password@]/path/to/file.sock[:/db_num]\n```\n\nFor example:\n\n1. `redis://localhost:6379`, or with password `redis://password@localhost:6379`\n2. `redis+socket://password@/path/to/file.sock:/0`\n3. cluster/sentinel `redis://host1:port1,host2:port2,host3:port3/0`\n4. cluster/sentinel with password `redis://pass@host1:port1,host2:port2,host3:port3/0`\n\n##### Memcache\n\nUse Memcache URL in the format:\n\n```\nmemcache://host1[:port1][,host2[:port2],...[,hostN[:portN]]]\n```\n\nFor example:\n\n1. `memcache://localhost:11211` for a single instance, or\n2. `memcache://10.0.0.1:11211,10.0.0.2:11211` for a cluster\n\n##### AMQP\n\nUse AMQP URL in the format:\n\n```\namqp://[username:password@]@host[:port]\n```\n\nFor example:\n\n1. `amqp://guest:guest@localhost:5672`\n\n> Keep in mind AMQP is not recommended as a result backend. See [Keeping Results](https://github.com/RichardKnop/machinery#keeping-results)\n\n##### MongoDB\n\nUse Mongodb URL in the format:\n\n```\nmongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]\n```\n\nFor example:\n\n1. `mongodb://localhost:27017/taskresults`\n\nSee [MongoDB docs](https://docs.mongodb.org/manual/reference/connection-string/) for more information.\n\n\n#### ResultsExpireIn\n\nHow long to store task results for in seconds. Defaults to `3600` (1 hour).\n\n#### AMQP\n\nRabbitMQ related configuration. Not necessary if you are using other broker/backend.\n\n* `Exchange`: exchange name, e.g. `machinery_exchange`\n* `ExchangeType`: exchange type, e.g. `direct`\n* `QueueBindingArguments`: an optional map of additional arguments used when binding to an AMQP queue\n* `BindingKey`: The queue is bind to the exchange with this key, e.g. `machinery_task`\n* `PrefetchCount`: How many tasks to prefetch (set to `1` if you have long running tasks)\n* `DelayedQueue`: delayed queue name to be used for task retry or delayed task (if empty it will follow auto create and delate delayed queues)\n\n#### DynamoDB\n\nDynamoDB related configuration. Not necessary if you are using other backend.\n* `TaskStatesTable`: Custom table name for saving task states. Default one is `task_states`, and make sure to create this table in your AWS admin first, using `TaskUUID` as table's primary key.\n* `GroupMetasTable`: Custom table name for saving group metas. Default one is `group_metas`, and make sure to create this table in your AWS admin first, using `GroupUUID` as table's primary key.\nFor example:\n\n```\ndynamodb:\n  task_states_table: 'task_states'\n  group_metas_table: 'group_metas'\n```\nIf these tables are not found, an fatal error would be thrown.\n\nIf you wish to expire the records, you can configure the `TTL` field in AWS admin for these tables. The `TTL` field is set based on the `ResultsExpireIn` value in the Server's config. See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html for more information.\n\n#### Redis\n\nRedis related configuration. Not necessary if you are using other backend.\n\nSee: [config](/v1/config/config.go) (TODO)\n\n#### GCPPubSub\n\nGCPPubSub related configuration. Not necessary if you are using other backend.\n\nSee: [config](/v1/config/config.go) (TODO)\n\n### Custom Logger\n\nYou can define a custom logger by implementing the following interface:\n\n```go\ntype Interface interface {\n  Print(...interface{})\n  Printf(string, ...interface{})\n  Println(...interface{})\n\n  Fatal(...interface{})\n  Fatalf(string, ...interface{})\n  Fatalln(...interface{})\n\n  Panic(...interface{})\n  Panicf(string, ...interface{})\n  Panicln(...interface{})\n}\n```\n\nThen just set the logger in your setup code by calling `Set` function exported by `github.com/RichardKnop/machinery/v1/log` package:\n\n```go\nlog.Set(myCustomLogger)\n```\n\n### Server\n\nA Machinery library must be instantiated before use. The way this is done is by creating a `Server` instance. `Server` is a base object which stores Machinery configuration and registered tasks. E.g.:\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/config\"\n  \"github.com/RichardKnop/machinery/v1\"\n)\n\nvar cnf = &config.Config{\n  Broker:        \"amqp://guest:guest@localhost:5672/\",\n  DefaultQueue:  \"machinery_tasks\",\n  ResultBackend: \"amqp://guest:guest@localhost:5672/\",\n  AMQP: &config.AMQPConfig{\n    Exchange:     \"machinery_exchange\",\n    ExchangeType: \"direct\",\n    BindingKey:   \"machinery_task\",\n  },\n}\n\nserver, err := machinery.NewServer(cnf)\nif err != nil {\n  // do something with the error\n}\n```\n\n### Workers\n\nIn order to consume tasks, you need to have one or more workers running. All you need to run a worker is a `Server` instance with registered tasks. E.g.:\n\n```go\nworker := server.NewWorker(\"worker_name\", 10)\nerr := worker.Launch()\nif err != nil {\n  // do something with the error\n}\n```\n\nEach worker will only consume registered tasks. For each task on the queue the Worker.Process() method will be run\nin a goroutine. Use the second parameter of `server.NewWorker` to limit the number of concurrently running Worker.Process()\ncalls (per worker). Example: 1 will serialize task execution while 0 makes the number of concurrently executed tasks unlimited (default).\n\n### Tasks\n\nTasks are a building block of Machinery applications. A task is a function which defines what happens when a worker receives a message.\n\nEach task needs to return an error as a last return value. In addition to error tasks can now return any number of arguments.\n\nExamples of valid tasks:\n\n```go\nfunc Add(args ...int64) (int64, error) {\n  sum := int64(0)\n  for _, arg := range args {\n    sum += arg\n  }\n  return sum, nil\n}\n\nfunc Multiply(args ...int64) (int64, error) {\n  sum := int64(1)\n  for _, arg := range args {\n    sum *= arg\n  }\n  return sum, nil\n}\n\n// You can use context.Context as first argument to tasks, useful for open tracing\nfunc TaskWithContext(ctx context.Context, arg Arg) error {\n  // ... use ctx ...\n  return nil\n}\n\n// Tasks need to return at least error as a minimal requirement\nfunc DummyTask(arg string) error {\n  return errors.New(arg)\n}\n\n// You can also return multiple results from the task\nfunc DummyTask2(arg1, arg2 string) (string, string, error) {\n  return arg1, arg2, nil\n}\n```\n\n#### Registering Tasks\n\nBefore your workers can consume a task, you need to register it with the server. This is done by assigning a task a unique name:\n\n```go\nserver.RegisterTasks(map[string]interface{}{\n  \"add\":      Add,\n  \"multiply\": Multiply,\n})\n```\n\nTasks can also be registered one by one:\n\n```go\nserver.RegisterTask(\"add\", Add)\nserver.RegisterTask(\"multiply\", Multiply)\n```\n\nSimply put, when a worker receives a message like this:\n\n```json\n{\n  \"UUID\": \"48760a1a-8576-4536-973b-da09048c2ac5\",\n  \"Name\": \"add\",\n  \"RoutingKey\": \"\",\n  \"ETA\": null,\n  \"GroupUUID\": \"\",\n  \"GroupTaskCount\": 0,\n  \"Args\": [\n    {\n      \"Type\": \"int64\",\n      \"Value\": 1,\n    },\n    {\n      \"Type\": \"int64\",\n      \"Value\": 1,\n    }\n  ],\n  \"Immutable\": false,\n  \"RetryCount\": 0,\n  \"RetryTimeout\": 0,\n  \"OnSuccess\": null,\n  \"OnError\": null,\n  \"ChordCallback\": null\n}\n```\n\nIt will call Add(1, 1). Each task should return an error as well so we can handle failures.\n\nIdeally, tasks should be idempotent which means there will be no unintended consequences when a task is called multiple times with the same arguments.\n\n#### Signatures\n\nA signature wraps calling arguments, execution options (such as immutability) and success/error callbacks of a task so it can be sent across the wire to workers. Task signatures implement a simple interface:\n\n```go\n// Arg represents a single argument passed to invocation fo a task\ntype Arg struct {\n  Type  string\n  Value interface{}\n}\n\n// Headers represents the headers which should be used to direct the task\ntype Headers map[string]interface{}\n\n// Signature represents a single task invocation\ntype Signature struct {\n  UUID           string\n  Name           string\n  RoutingKey     string\n  ETA            *time.Time\n  GroupUUID      string\n  GroupTaskCount int\n  Args           []Arg\n  Headers        Headers\n  Immutable      bool\n  RetryCount     int\n  RetryTimeout   int\n  OnSuccess      []*Signature\n  OnError        []*Signature\n  ChordCallback  *Signature\n}\n```\n\n`UUID` is a unique ID of a task. You can either set it yourself or it will be automatically generated.\n\n`Name` is the unique task name by which it is registered against a Server instance.\n\n`RoutingKey` is used for routing a task to correct queue. If you leave it empty, the default behaviour will be to set it to the default queue's binding key for direct exchange type and to the default queue name for other exchange types.\n\n`ETA` is  a timestamp used for delaying a task. if it's nil, the task will be published for workers to consume immediately. If it is set, the task will be delayed until the ETA timestamp.\n\n`GroupUUID`, `GroupTaskCount` are useful for creating groups of tasks.\n\n`Args` is a list of arguments that will be passed to the task when it is executed by a worker.\n\n`Headers` is a list of headers that will be used when publishing the task to AMQP queue.\n\n`Immutable` is a flag which defines whether a result of the executed task can be modified or not. This is important with `OnSuccess` callbacks. Immutable task will not pass its result to its success callbacks while a mutable task will prepend its result to args sent to callback tasks. Long story short, set Immutable to false if you want to pass result of the first task in a chain to the second task.\n\n`RetryCount` specifies how many times a failed task should be retried (defaults to 0). Retry attempts will be spaced out in time, after each failure another attempt will be scheduled further to the future.\n\n`RetryTimeout` specifies how long to wait before resending task to the queue for retry attempt. Default behaviour is to use fibonacci sequence to increase the timeout after each failed retry attempt.\n\n`OnSuccess` defines tasks which will be called after the task has executed successfully. It is a slice of task signature structs.\n\n`OnError` defines tasks which will be called after the task execution fails. The first argument passed to error callbacks will be the error string returned from the failed task.\n\n`ChordCallback` is used to create a callback to a group of tasks.\n\n#### Supported Types\n\nMachinery encodes tasks to JSON before sending them to the broker. Task results are also stored in the backend as JSON encoded strings. Therefor only types with native JSON representation can be supported. Currently supported types are:\n\n* `bool`\n* `int`\n* `int8`\n* `int16`\n* `int32`\n* `int64`\n* `uint`\n* `uint8`\n* `uint16`\n* `uint32`\n* `uint64`\n* `float32`\n* `float64`\n* `string`\n* `[]bool`\n* `[]int`\n* `[]int8`\n* `[]int16`\n* `[]int32`\n* `[]int64`\n* `[]uint`\n* `[]uint8`\n* `[]uint16`\n* `[]uint32`\n* `[]uint64`\n* `[]float32`\n* `[]float64`\n* `[]string`\n\n#### Sending Tasks\n\nTasks can be called by passing an instance of `Signature` to an `Server` instance. E.g:\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\nsignature := &tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n  },\n}\n\nasyncResult, err := server.SendTask(signature)\nif err != nil {\n  // failed to send the task\n  // do something with the error\n}\n```\n\n#### Delayed Tasks\n\nYou can delay a task by setting the `ETA` timestamp field on the task signature.\n\n```go\n// Delay the task by 5 seconds\neta := time.Now().UTC().Add(time.Second * 5)\nsignature.ETA = &eta\n```\n\n#### Retry Tasks\n\nYou can set a number of retry attempts before declaring task as failed. Fibonacci sequence will be used to space out retry requests over time. (See `RetryTimeout` for details.)\n\n```go\n// If the task fails, retry it up to 3 times\nsignature.RetryCount = 3\n```\n\nAlternatively, you can return `tasks.ErrRetryTaskLater` from your task and specify duration after which the task should be retried, e.g.:\n\n```go\nreturn tasks.NewErrRetryTaskLater(\"some error\", 4 * time.Hour)\n```\n\n#### Get Pending Tasks\n\nTasks currently waiting in the queue to be consumed by workers can be inspected, e.g.:\n\n```go\nserver.GetBroker().GetPendingTasks(\"some_queue\")\n```\n\n> Currently only supported by Redis broker.\n\n#### Keeping Results\n\nIf you configure a result backend, the task states and results will be persisted. Possible states:\n\n```go\nconst (\n\t// StatePending - initial state of a task\n\tStatePending = \"PENDING\"\n\t// StateReceived - when task is received by a worker\n\tStateReceived = \"RECEIVED\"\n\t// StateStarted - when the worker starts processing the task\n\tStateStarted = \"STARTED\"\n\t// StateRetry - when failed task has been scheduled for retry\n\tStateRetry = \"RETRY\"\n\t// StateSuccess - when the task is processed successfully\n\tStateSuccess = \"SUCCESS\"\n\t// StateFailure - when processing of the task fails\n\tStateFailure = \"FAILURE\"\n)\n```\n\n> When using AMQP as a result backend, task states will be persisted in separate queues for each task. Although RabbitMQ can scale up to thousands of queues, it is strongly advised to use a better suited result backend (e.g. Memcache) when you are expecting to run a large number of parallel tasks.\n\n```go\n// TaskResult represents an actual return value of a processed task\ntype TaskResult struct {\n  Type  string      `bson:\"type\"`\n  Value interface{} `bson:\"value\"`\n}\n\n// TaskState represents a state of a task\ntype TaskState struct {\n  TaskUUID  string        `bson:\"_id\"`\n  State     string        `bson:\"state\"`\n  Results   []*TaskResult `bson:\"results\"`\n  Error     string        `bson:\"error\"`\n}\n\n// GroupMeta stores useful metadata about tasks within the same group\n// E.g. UUIDs of all tasks which are used in order to check if all tasks\n// completed successfully or not and thus whether to trigger chord callback\ntype GroupMeta struct {\n  GroupUUID      string   `bson:\"_id\"`\n  TaskUUIDs      []string `bson:\"task_uuids\"`\n  ChordTriggered bool     `bson:\"chord_triggered\"`\n  Lock           bool     `bson:\"lock\"`\n}\n```\n\n`TaskResult` represents a slice of return values of a processed task.\n\n`TaskState` struct will be serialized and stored every time a task state changes.\n\n`GroupMeta` stores useful metadata about tasks within the same group. E.g. UUIDs of all tasks which are used in order to check if all tasks completed successfully or not and thus whether to trigger chord callback.\n\n`AsyncResult` object allows you to check for the state of a task:\n\n```go\ntaskState := asyncResult.GetState()\nfmt.Printf(\"Current state of %v task is:\\n\", taskState.TaskUUID)\nfmt.Println(taskState.State)\n```\n\nThere are couple of convenient methods to inspect the task status:\n\n```go\nasyncResult.GetState().IsCompleted()\nasyncResult.GetState().IsSuccess()\nasyncResult.GetState().IsFailure()\n```\n\nYou can also do a synchronous blocking call to wait for a task result:\n\n```go\nresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\nif err != nil {\n  // getting result of a task failed\n  // do something with the error\n}\nfor _, result := range results {\n  fmt.Println(result.Interface())\n}\n```\n\n#### Error Handling\n\nWhen a task returns with an error, the default behavior is to first attempty to retry the task if it's retriable, otherwise log the error and then eventually call any error callbacks.\n\nTo customize this, you can set a custom error handler on the worker which can do more than just logging after retries fail and error callbacks are trigerred:\n\n```go\nworker.SetErrorHandler(func (err error) {\n  customHandler(err)\n})\n```\n\n### Workflows\n\nRunning a single asynchronous task is fine but often you will want to design a workflow of tasks to be executed in an orchestrated way. There are couple of useful functions to help you design workflows.\n\n#### Groups\n\n`Group` is a set of tasks which will be executed in parallel, independent of each other. E.g.:\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/tasks\"\n  \"github.com/RichardKnop/machinery/v1\"\n)\n\nsignature1 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n  },\n}\n\nsignature2 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n  },\n}\n\ngroup, _ := tasks.NewGroup(&signature1, &signature2)\nasyncResults, err := server.SendGroup(group, 0) //The second parameter specifies the number of concurrent sending tasks. 0 means unlimited.\nif err != nil {\n  // failed to send the group\n  // do something with the error\n}\n```\n\n`SendGroup` returns a slice of `AsyncResult` objects. So you can do a blocking call and wait for the result of groups tasks:\n\n```go\nfor _, asyncResult := range asyncResults {\n  results, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n  if err != nil {\n    // getting result of a task failed\n    // do something with the error\n  }\n  for _, result := range results {\n    fmt.Println(result.Interface())\n  }\n}\n```\n\n#### Chords\n\n`Chord` allows you to define a callback to be executed after all tasks in a group finished processing, e.g.:\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/tasks\"\n  \"github.com/RichardKnop/machinery/v1\"\n)\n\nsignature1 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n  },\n}\n\nsignature2 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n  },\n}\n\nsignature3 := tasks.Signature{\n  Name: \"multiply\",\n}\n\ngroup := tasks.NewGroup(&signature1, &signature2)\nchord, _ := tasks.NewChord(group, &signature3)\nchordAsyncResult, err := server.SendChord(chord, 0) //The second parameter specifies the number of concurrent sending tasks. 0 means unlimited.\nif err != nil {\n  // failed to send the chord\n  // do something with the error\n}\n```\n\nThe above example executes task1 and task2 in parallel, aggregates their results and passes them to task3. Therefore what would end up happening is:\n\n```\nmultiply(add(1, 1), add(5, 5))\n```\n\nMore explicitly:\n\n```\n(1 + 1) * (5 + 5) = 2 * 10 = 20\n```\n\n`SendChord` returns `ChordAsyncResult` which follows AsyncResult's interface. So you can do a blocking call and wait for the result of the callback:\n\n```go\nresults, err := chordAsyncResult.Get(time.Duration(time.Millisecond * 5))\nif err != nil {\n  // getting result of a chord failed\n  // do something with the error\n}\nfor _, result := range results {\n  fmt.Println(result.Interface())\n}\n```\n\n#### Chains\n\n`Chain` is simply a set of tasks which will be executed one by one, each successful task triggering the next task in the chain. E.g.:\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/tasks\"\n  \"github.com/RichardKnop/machinery/v1\"\n)\n\nsignature1 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n  },\n}\n\nsignature2 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n  },\n}\n\nsignature3 := tasks.Signature{\n  Name: \"multiply\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 4,\n    },\n  },\n}\n\nchain, _ := tasks.NewChain(&signature1, &signature2, &signature3)\nchainAsyncResult, err := server.SendChain(chain)\nif err != nil {\n  // failed to send the chain\n  // do something with the error\n}\n```\n\nThe above example executes task1, then task2 and then task3. When a task is completed successfully, the result is appended to the end of list of arguments for the next task in the chain. Therefore what would end up happening is:\n\n```\nmultiply(4, add(5, 5, add(1, 1)))\n```\n\nMore explicitly:\n\n```\n  4 * (5 + 5 + (1 + 1))   # task1: add(1, 1)        returns 2\n= 4 * (5 + 5 + 2)         # task2: add(5, 5, 2)     returns 12\n= 4 * (12)                # task3: multiply(4, 12)  returns 48\n= 48\n```\n\n`SendChain` returns `ChainAsyncResult` which follows AsyncResult's interface. So you can do a blocking call and wait for the result of the whole chain:\n\n```go\nresults, err := chainAsyncResult.Get(time.Duration(time.Millisecond * 5))\nif err != nil {\n  // getting result of a chain failed\n  // do something with the error\n}\nfor _, result := range results {\n  fmt.Println(result.Interface())\n}\n```\n\n### Periodic Tasks & Workflows\n\nMachinery now supports scheduling periodic tasks and workflows. See examples bellow.\n\n#### Periodic Tasks\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\nsignature := &tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n  },\n}\nerr := server.RegisterPeriodicTask(\"0 6 * * ?\", \"periodic-task\", signature)\nif err != nil {\n  // failed to register periodic task\n}\n```\n\n#### Periodic Groups\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/tasks\"\n  \"github.com/RichardKnop/machinery/v1\"\n)\n\nsignature1 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n  },\n}\n\nsignature2 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n  },\n}\n\ngroup, _ := tasks.NewGroup(&signature1, &signature2)\nerr := server.RegisterPeriodicGroup(\"0 6 * * ?\", \"periodic-group\", group)\nif err != nil {\n  // failed to register periodic group\n}\n```\n\n#### Periodic Chains\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/tasks\"\n  \"github.com/RichardKnop/machinery/v1\"\n)\n\nsignature1 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n  },\n}\n\nsignature2 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n  },\n}\n\nsignature3 := tasks.Signature{\n  Name: \"multiply\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 4,\n    },\n  },\n}\n\nchain, _ := tasks.NewChain(&signature1, &signature2, &signature3)\nerr := server.RegisterPeriodicChain(\"0 6 * * ?\", \"periodic-chain\", chain)\nif err != nil {\n  // failed to register periodic chain\n}\n```\n\n#### Chord\n\n```go\nimport (\n  \"github.com/RichardKnop/machinery/v1/tasks\"\n  \"github.com/RichardKnop/machinery/v1\"\n)\n\nsignature1 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n    {\n      Type:  \"int64\",\n      Value: 1,\n    },\n  },\n}\n\nsignature2 := tasks.Signature{\n  Name: \"add\",\n  Args: []tasks.Arg{\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n    {\n      Type:  \"int64\",\n      Value: 5,\n    },\n  },\n}\n\nsignature3 := tasks.Signature{\n  Name: \"multiply\",\n}\n\ngroup := tasks.NewGroup(&signature1, &signature2)\nchord, _ := tasks.NewChord(group, &signature3)\nerr := server.RegisterPeriodicChord(\"0 6 * * ?\", \"periodic-chord\", chord)\nif err != nil {\n  // failed to register periodic chord\n}\n```\n\n### Development\n\n#### Requirements\n\n* Go\n* RabbitMQ (optional)\n* Redis\n* Memcached (optional)\n* MongoDB (optional)\n\nOn OS X systems, you can install requirements using [Homebrew](http://brew.sh/):\n\n```sh\nbrew install go\nbrew install rabbitmq\nbrew install redis\nbrew install memcached\nbrew install mongodb\n```\n\nOr optionally use the corresponding [Docker](http://docker.io/) containers:\n\n```\ndocker run -d -p 5672:5672 rabbitmq\ndocker run -d -p 6379:6379 redis\ndocker run -d -p 11211:11211 memcached\ndocker run -d -p 27017:27017 mongo\ndocker run -d -p 6831:6831/udp -p 16686:16686 jaegertracing/all-in-one:latest\n```\n\n#### Dependencies\n\nSince Go 1.11, a new recommended dependency management system is via [modules](https://github.com/golang/go/wiki/Modules).\n\nThis is one of slight weaknesses of Go as dependency management is not a solved problem. Previously Go was officially recommending to use the [dep tool](https://github.com/golang/dep) but that has been abandoned now in favor of modules.\n\n#### Testing\n\nEasiest (and platform agnostic) way to run tests is via `docker-compose`:\n\n```sh\nmake ci\n```\n\nThis will basically run docker-compose command:\n\n```sh\n(docker-compose -f docker-compose.test.yml -p machinery_ci up --build -d) && (docker logs -f machinery_sut &) && (docker wait machinery_sut)\n```\n\nAlternative approach is to setup a development environment on your machine.\n\nIn order to enable integration tests, you will need to install all required services (RabbitMQ, Redis, Memcache, MongoDB) and export these environment variables:\n\n```sh\nexport AMQP_URL=amqp://guest:guest@localhost:5672/\nexport REDIS_URL=localhost:6379\nexport MEMCACHE_URL=localhost:11211\nexport MONGODB_URL=localhost:27017\n```\n\nTo run integration tests against an SQS instance, you will need to create a \"test_queue\" in SQS and export these environment variables:\n\n```sh\nexport SQS_URL=https://YOUR_SQS_URL\nexport AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID\nexport AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY\nexport AWS_DEFAULT_REGION=YOUR_AWS_DEFAULT_REGION\n```\n\nThen just run:\n\n```sh\nmake test\n```\n\nIf the environment variables are not exported, `make test` will only run unit tests.\n"
  },
  {
    "path": "docker-compose.test.yml",
    "content": "version: \"2\"\n\nservices:\n  sut:\n    container_name: machinery_sut\n    image: machinery_sut:latest\n    volumes:\n      - \"./:/go/src/github.com/RichardKnop/machinery\"\n    depends_on:\n      - rabbitmq\n      - redis\n      - memcached\n      - mongo\n      - gcppubsub\n    links:\n      - rabbitmq\n      - redis\n      - memcached\n      - mongo\n      - gcppubsub\n    build:\n      context: .\n      dockerfile: ./Dockerfile.test\n    environment:\n      AMQP_URLS: 'amqp://guest:guest@dummy:5672/,amqp://guest:guest@rabbitmq:5672/'\n      AMQP_URLS_SEPARATOR: ','\n      REDIS_URL: 'redis:6379'\n      MEMCACHE_URL: 'memcached:11211'\n      MONGODB_URL: 'mongo:27017'\n      SQS_URL: ${SQS_URL}\n      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}\n      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}\n      AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION}\n      AWS_REGION: 'us-west-2'\n      GCPPUBSUB_URL: 'gcppubsub://example-project/test_subscription_queue'\n      GCPPUBSUB_TOPIC: 'test_topic_queue'\n      PUBSUB_EMULATOR_HOST: 'gcppubsub:8085'\n\n  rabbitmq:\n    container_name: machinery_sut_rabbitmq\n    image: rabbitmq\n    environment:\n      - RABBITMQ_DEFAULT_USER=guest\n      - RABBITMQ_DEFAULT_PASS=guest\n    logging:\n      driver: none\n\n  redis:\n    container_name: machinery_sut_redis\n    image: redis\n    logging:\n      driver: none\n\n  memcached:\n    container_name: machinery_sut_memcached\n    image: memcached\n    logging:\n      driver: none\n\n  mongo:\n    container_name: machinery_sut_mongo\n    image: mongo\n    logging:\n      driver: none\n\n  gcppubsub:\n    container_name: machinery_sut_gcppubsub\n    build:\n      context: .\n      dockerfile: ./Dockerfile.gcppubsub\n    logging:\n      driver: none"
  },
  {
    "path": "example/amqp/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/urfave/cli\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\n\texampletasks \"github.com/RichardKnop/machinery/example/tasks\"\n\ttracers \"github.com/RichardKnop/machinery/example/tracers\"\n\topentracing \"github.com/opentracing/opentracing-go\"\n\topentracing_log \"github.com/opentracing/opentracing-go/log\"\n)\n\nvar (\n\tapp *cli.App\n)\n\nfunc init() {\n\t// Initialise a CLI app\n\tapp = cli.NewApp()\n\tapp.Name = \"machinery\"\n\tapp.Usage = \"machinery worker and send example tasks with machinery send\"\n\tapp.Version = \"0.0.0\"\n}\n\nfunc main() {\n\t// Set the CLI app commands\n\tapp.Commands = []cli.Command{\n\t\t{\n\t\t\tName:  \"worker\",\n\t\t\tUsage: \"launch machinery worker\",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := worker(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:  \"send\",\n\t\t\tUsage: \"send example tasks \",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := send(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t}\n\n\t// Run the CLI app\n\tapp.Run(os.Args)\n}\n\nfunc startServer() (*machinery.Server, error) {\n\tcnf := &config.Config{\n\t\tBroker:          \"amqp://guest:guest@localhost:5672/\",\n\t\tDefaultQueue:    \"machinery_tasks\",\n\t\tResultBackend:   \"amqp://guest:guest@localhost:5672/\",\n\t\tResultsExpireIn: 3600,\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"machinery_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"machinery_task\",\n\t\t\tPrefetchCount: 3,\n\t\t},\n\t}\n\n\tserver, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Register tasks\n\ttasks := map[string]interface{}{\n\t\t\"add\":               exampletasks.Add,\n\t\t\"multiply\":          exampletasks.Multiply,\n\t\t\"sum_ints\":          exampletasks.SumInts,\n\t\t\"sum_floats\":        exampletasks.SumFloats,\n\t\t\"concat\":            exampletasks.Concat,\n\t\t\"split\":             exampletasks.Split,\n\t\t\"panic_task\":        exampletasks.PanicTask,\n\t\t\"long_running_task\": exampletasks.LongRunningTask,\n\t}\n\n\treturn server, server.RegisterTasks(tasks)\n}\n\nfunc worker() error {\n\tconsumerTag := \"machinery_worker\"\n\n\tcleanup, err := tracers.SetupTracer(consumerTag)\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// The second argument is a consumer tag\n\t// Ideally, each worker should have a unique tag (worker1, worker2 etc)\n\tworker := server.NewWorker(consumerTag, 0)\n\n\t// Here we inject some custom code for error handling,\n\t// start and end of task hooks, useful for metrics for example.\n\terrorhandler := func(err error) {\n\t\tlog.ERROR.Println(\"I am an error handler:\", err)\n\t}\n\n\tpretaskhandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am a start of task handler for:\", signature.Name)\n\t}\n\n\tposttaskhandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am an end of task handler for:\", signature.Name)\n\t}\n\n\tworker.SetPostTaskHandler(posttaskhandler)\n\tworker.SetErrorHandler(errorhandler)\n\tworker.SetPreTaskHandler(pretaskhandler)\n\n\treturn worker.Launch()\n}\n\nfunc send() error {\n\tcleanup, err := tracers.SetupTracer(\"sender\")\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar (\n\t\taddTask0, addTask1, addTask2                      tasks.Signature\n\t\tmultiplyTask0, multiplyTask1                      tasks.Signature\n\t\tsumIntsTask, sumFloatsTask, concatTask, splitTask tasks.Signature\n\t\tpanicTask                                         tasks.Signature\n\t\tlongRunningTask                                   tasks.Signature\n\t)\n\n\tvar initTasks = func() {\n\t\taddTask0 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask1 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask2 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 5,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 6,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask0 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 4,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask1 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t}\n\n\t\tsumIntsTask = tasks.Signature{\n\t\t\tName: \"sum_ints\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]int64\",\n\t\t\t\t\tValue: []int64{1, 2},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsumFloatsTask = tasks.Signature{\n\t\t\tName: \"sum_floats\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]float64\",\n\t\t\t\t\tValue: []float64{1.5, 2.7},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tconcatTask = tasks.Signature{\n\t\t\tName: \"concat\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]string\",\n\t\t\t\t\tValue: []string{\"foo\", \"bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsplitTask = tasks.Signature{\n\t\t\tName: \"split\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"string\",\n\t\t\t\t\tValue: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tpanicTask = tasks.Signature{\n\t\t\tName: \"panic_task\",\n\t\t}\n\n\t\tlongRunningTask = tasks.Signature{\n\t\t\tName: \"long_running_task\",\n\t\t}\n\t}\n\n\t/*\n\t * Lets start a span representing this run of the `send` command and\n\t * set a batch id as baggage so it can travel all the way into\n\t * the worker functions.\n\t */\n\tspan, ctx := opentracing.StartSpanFromContext(context.Background(), \"send\")\n\tdefer span.Finish()\n\n\tbatchID := uuid.New().String()\n\tspan.SetBaggageItem(\"batch.id\", batchID)\n\tspan.LogFields(opentracing_log.String(\"batch.id\", batchID))\n\n\tlog.INFO.Println(\"Starting batch:\", batchID)\n\t/*\n\t * First, let's try sending a single task\n\t */\n\tinitTasks()\n\n\tlog.INFO.Println(\"Single task:\")\n\n\tasyncResult, err := server.SendTaskWithContext(ctx, &addTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"1 + 1 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Try couple of tasks with a slice argument and slice return value\n\t */\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumIntsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1, 2]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumFloatsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1.5, 2.7]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &concatTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"concat([\\\"foo\\\", \\\"bar\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &splitTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"split([\\\"foo\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Now let's explore ways of sending multiple tasks\n\t */\n\n\t// Now let's try a parallel execution\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks (parallel execution):\")\n\n\tgroup, err := tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tasyncResults, err := server.SendGroupWithContext(ctx, group, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send group: %s\", err.Error())\n\t}\n\n\tfor _, asyncResult := range asyncResults {\n\t\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t\t}\n\t\tlog.INFO.Printf(\n\t\t\t\"%v + %v = %v\\n\",\n\t\t\tasyncResult.Signature.Args[0].Value,\n\t\t\tasyncResult.Signature.Args[1].Value,\n\t\t\ttasks.HumanReadableResults(results),\n\t\t)\n\t}\n\n\t// Now let's try a group with a chord\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks with a callback (chord):\")\n\n\tgroup, err = tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tchord, err := tasks.NewChord(group, &multiplyTask1)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chord: %s\", err)\n\t}\n\n\tchordAsyncResult, err := server.SendChordWithContext(ctx, chord, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chord: %s\", err.Error())\n\t}\n\n\tresults, err = chordAsyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chord result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(1 + 1) * (2 + 2) * (5 + 6) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Now let's try chaining task results\n\tinitTasks()\n\tlog.INFO.Println(\"Chain of tasks:\")\n\n\tchain, err := tasks.NewChain(&addTask0, &addTask1, &addTask2, &multiplyTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chain: %s\", err)\n\t}\n\n\tchainAsyncResult, err := server.SendChainWithContext(ctx, chain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chain: %s\", err.Error())\n\t}\n\n\tresults, err = chainAsyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chain result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(((1 + 1) + (2 + 2)) + (5 + 6)) * 4 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Let's try a task which throws panic to make sure stack trace is not lost\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &panicTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\t_, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err == nil {\n\t\treturn errors.New(\"Error should not be nil if task panicked\")\n\t}\n\tlog.INFO.Printf(\"Task panicked and returned error = %v\\n\", err.Error())\n\n\t// Let's try a long running task\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &longRunningTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting long running task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"Long running task returned = %v\\n\", tasks.HumanReadableResults(results))\n\n\treturn nil\n}\n"
  },
  {
    "path": "example/redis/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/urfave/cli\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\n\texampletasks \"github.com/RichardKnop/machinery/example/tasks\"\n\ttracers \"github.com/RichardKnop/machinery/example/tracers\"\n\topentracing \"github.com/opentracing/opentracing-go\"\n\topentracing_log \"github.com/opentracing/opentracing-go/log\"\n)\n\nvar (\n\tapp *cli.App\n)\n\nfunc init() {\n\t// Initialise a CLI app\n\tapp = cli.NewApp()\n\tapp.Name = \"machinery\"\n\tapp.Usage = \"machinery worker and send example tasks with machinery send\"\n\tapp.Version = \"0.0.0\"\n}\n\nfunc main() {\n\t// Set the CLI app commands\n\tapp.Commands = []cli.Command{\n\t\t{\n\t\t\tName:  \"worker\",\n\t\t\tUsage: \"launch machinery worker\",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := worker(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:  \"send\",\n\t\t\tUsage: \"send example tasks \",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := send(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t}\n\n\t// Run the CLI app\n\tapp.Run(os.Args)\n}\n\nfunc startServer() (*machinery.Server, error) {\n\tcnf := &config.Config{\n\t\tDefaultQueue:    \"machinery_tasks\",\n\t\tResultsExpireIn: 3600,\n\t\tBroker:          \"redis://localhost:6379\",\n\t\tResultBackend:   \"redis://localhost:6379\",\n\t\tRedis: &config.RedisConfig{\n\t\t\tMaxIdle:                3,\n\t\t\tIdleTimeout:            240,\n\t\t\tReadTimeout:            15,\n\t\t\tWriteTimeout:           15,\n\t\t\tConnectTimeout:         15,\n\t\t\tNormalTasksPollPeriod:  1000,\n\t\t\tDelayedTasksPollPeriod: 500,\n\t\t},\n\t}\n\n\tserver, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Register tasks\n\ttasks := map[string]interface{}{\n\t\t\"add\":               exampletasks.Add,\n\t\t\"multiply\":          exampletasks.Multiply,\n\t\t\"sum_ints\":          exampletasks.SumInts,\n\t\t\"sum_floats\":        exampletasks.SumFloats,\n\t\t\"concat\":            exampletasks.Concat,\n\t\t\"split\":             exampletasks.Split,\n\t\t\"panic_task\":        exampletasks.PanicTask,\n\t\t\"long_running_task\": exampletasks.LongRunningTask,\n\t}\n\n\treturn server, server.RegisterTasks(tasks)\n}\n\nfunc worker() error {\n\tconsumerTag := \"machinery_worker\"\n\n\tcleanup, err := tracers.SetupTracer(consumerTag)\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// The second argument is a consumer tag\n\t// Ideally, each worker should have a unique tag (worker1, worker2 etc)\n\tworker := server.NewWorker(consumerTag, 0)\n\n\t// Here we inject some custom code for error handling,\n\t// start and end of task hooks, useful for metrics for example.\n\terrorhandler := func(err error) {\n\t\tlog.ERROR.Println(\"I am an error handler:\", err)\n\t}\n\n\tpretaskhandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am a start of task handler for:\", signature.Name)\n\t}\n\n\tposttaskhandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am an end of task handler for:\", signature.Name)\n\t}\n\n\tworker.SetPostTaskHandler(posttaskhandler)\n\tworker.SetErrorHandler(errorhandler)\n\tworker.SetPreTaskHandler(pretaskhandler)\n\n\treturn worker.Launch()\n}\n\nfunc send() error {\n\tcleanup, err := tracers.SetupTracer(\"sender\")\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar (\n\t\taddTask0, addTask1, addTask2                      tasks.Signature\n\t\tmultiplyTask0, multiplyTask1                      tasks.Signature\n\t\tsumIntsTask, sumFloatsTask, concatTask, splitTask tasks.Signature\n\t\tpanicTask                                         tasks.Signature\n\t\tlongRunningTask                                   tasks.Signature\n\t)\n\n\tvar initTasks = func() {\n\t\taddTask0 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask1 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask2 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 5,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 6,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask0 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 4,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask1 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t}\n\n\t\tsumIntsTask = tasks.Signature{\n\t\t\tName: \"sum_ints\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]int64\",\n\t\t\t\t\tValue: []int64{1, 2},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsumFloatsTask = tasks.Signature{\n\t\t\tName: \"sum_floats\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]float64\",\n\t\t\t\t\tValue: []float64{1.5, 2.7},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tconcatTask = tasks.Signature{\n\t\t\tName: \"concat\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]string\",\n\t\t\t\t\tValue: []string{\"foo\", \"bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsplitTask = tasks.Signature{\n\t\t\tName: \"split\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"string\",\n\t\t\t\t\tValue: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tpanicTask = tasks.Signature{\n\t\t\tName: \"panic_task\",\n\t\t}\n\n\t\tlongRunningTask = tasks.Signature{\n\t\t\tName: \"long_running_task\",\n\t\t}\n\t}\n\n\t/*\n\t * Lets start a span representing this run of the `send` command and\n\t * set a batch id as baggage so it can travel all the way into\n\t * the worker functions.\n\t */\n\tspan, ctx := opentracing.StartSpanFromContext(context.Background(), \"send\")\n\tdefer span.Finish()\n\n\tbatchID := uuid.New().String()\n\tspan.SetBaggageItem(\"batch.id\", batchID)\n\tspan.LogFields(opentracing_log.String(\"batch.id\", batchID))\n\n\tlog.INFO.Println(\"Starting batch:\", batchID)\n\t/*\n\t * First, let's try sending a single task\n\t */\n\tinitTasks()\n\n\tlog.INFO.Println(\"Single task:\")\n\n\tasyncResult, err := server.SendTaskWithContext(ctx, &addTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"1 + 1 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Try couple of tasks with a slice argument and slice return value\n\t */\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumIntsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1, 2]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumFloatsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1.5, 2.7]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &concatTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"concat([\\\"foo\\\", \\\"bar\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &splitTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"split([\\\"foo\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Now let's explore ways of sending multiple tasks\n\t */\n\n\t// Now let's try a parallel execution\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks (parallel execution):\")\n\n\tgroup, err := tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tasyncResults, err := server.SendGroupWithContext(ctx, group, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send group: %s\", err.Error())\n\t}\n\n\tfor _, asyncResult := range asyncResults {\n\t\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t\t}\n\t\tlog.INFO.Printf(\n\t\t\t\"%v + %v = %v\\n\",\n\t\t\tasyncResult.Signature.Args[0].Value,\n\t\t\tasyncResult.Signature.Args[1].Value,\n\t\t\ttasks.HumanReadableResults(results),\n\t\t)\n\t}\n\n\t// Now let's try a group with a chord\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks with a callback (chord):\")\n\n\tgroup, err = tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tchord, err := tasks.NewChord(group, &multiplyTask1)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chord: %s\", err)\n\t}\n\n\tchordAsyncResult, err := server.SendChordWithContext(ctx, chord, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chord: %s\", err.Error())\n\t}\n\n\tresults, err = chordAsyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chord result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(1 + 1) * (2 + 2) * (5 + 6) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Now let's try chaining task results\n\tinitTasks()\n\tlog.INFO.Println(\"Chain of tasks:\")\n\n\tchain, err := tasks.NewChain(&addTask0, &addTask1, &addTask2, &multiplyTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chain: %s\", err)\n\t}\n\n\tchainAsyncResult, err := server.SendChainWithContext(ctx, chain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chain: %s\", err.Error())\n\t}\n\n\tresults, err = chainAsyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chain result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(((1 + 1) + (2 + 2)) + (5 + 6)) * 4 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Let's try a task which throws panic to make sure stack trace is not lost\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &panicTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\t_, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err == nil {\n\t\treturn errors.New(\"Error should not be nil if task panicked\")\n\t}\n\tlog.INFO.Printf(\"Task panicked and returned error = %v\\n\", err.Error())\n\n\t// Let's try a long running task\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &longRunningTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting long running task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"Long running task returned = %v\\n\", tasks.HumanReadableResults(results))\n\n\treturn nil\n}\n"
  },
  {
    "path": "example/tasks/tasks.go",
    "content": "package exampletasks\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/log\"\n)\n\n// Add ...\nfunc Add(args ...int64) (int64, error) {\n\tsum := int64(0)\n\tfor _, arg := range args {\n\t\tsum += arg\n\t}\n\treturn sum, nil\n}\n\n// Multiply ...\nfunc Multiply(args ...int64) (int64, error) {\n\tsum := int64(1)\n\tfor _, arg := range args {\n\t\tsum *= arg\n\t}\n\treturn sum, nil\n}\n\n// SumInts ...\nfunc SumInts(numbers []int64) (int64, error) {\n\tvar sum int64\n\tfor _, num := range numbers {\n\t\tsum += num\n\t}\n\treturn sum, nil\n}\n\n// SumFloats ...\nfunc SumFloats(numbers []float64) (float64, error) {\n\tvar sum float64\n\tfor _, num := range numbers {\n\t\tsum += num\n\t}\n\treturn sum, nil\n}\n\n// Concat ...\nfunc Concat(strs []string) (string, error) {\n\tvar res string\n\tfor _, s := range strs {\n\t\tres += s\n\t}\n\treturn res, nil\n}\n\n// Split ...\nfunc Split(str string) ([]string, error) {\n\treturn strings.Split(str, \"\"), nil\n}\n\n// PanicTask ...\nfunc PanicTask() (string, error) {\n\tpanic(errors.New(\"oops\"))\n}\n\n// LongRunningTask ...\nfunc LongRunningTask() error {\n\tlog.INFO.Print(\"Long running task started\")\n\tfor i := 0; i < 10; i++ {\n\t\tlog.INFO.Print(10 - i)\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\tlog.INFO.Print(\"Long running task finished\")\n\treturn nil\n}\n"
  },
  {
    "path": "example/tracers/jaeger.go",
    "content": "package tracers\n\n// Uncomment the import statement for the jaeger tracer.\n// make sure you run dep ensure to pull in the jaeger client\n//\n// import (\n// \tjaeger \"github.com/uber/jaeger-client-go\"\n// \tjaegercfg \"github.com/uber/jaeger-client-go/config\"\n// )\n\n// SetupTracer is the place where you'd setup your specific tracer.\n// The jaeger tracer is given as an example.\n// To capture the jaeger traces you should run the jaeger backend.\n// This can be done using the following docker command:\n//\n// `docker run -ti --rm -p6831:6831/udp -p16686:16686 jaegertracing/all-in-one:latest`\n//\n// The collector will be listening on localhost:6831\n// and the query UI is reachable on localhost:16686.\nfunc SetupTracer(serviceName string) (func(), error) {\n\n\t// Jaeger setup code\n\t//\n\t// config := jaegercfg.Configuration{\n\t// \tSampler: &jaegercfg.SamplerConfig{\n\t// \t\tType:  jaeger.SamplerTypeConst,\n\t// \t\tParam: 1,\n\t// \t},\n\t// }\n\n\t// closer, err := config.InitGlobalTracer(serviceName)\n\t// if err != nil {\n\t// \treturn nil, err\n\t// }\n\n\tcleanupFunc := func() {\n\t\t// closer.Close()\n\t}\n\n\treturn cleanupFunc, nil\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/RichardKnop/machinery\n\ngo 1.22\n\ntoolchain go1.24.1\n\nrequire (\n\tcloud.google.com/go/pubsub v1.10.0\n\tgithub.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae\n\tgithub.com/aws/aws-sdk-go v1.55.6\n\tgithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b\n\tgithub.com/go-redsync/redsync/v4 v4.8.1\n\tgithub.com/gomodule/redigo v1.9.2\n\tgithub.com/google/uuid v1.2.0\n\tgithub.com/kelseyhightower/envconfig v1.4.0\n\tgithub.com/opentracing/opentracing-go v1.2.0\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/rabbitmq/amqp091-go v1.9.0\n\tgithub.com/redis/go-redis/v9 v9.0.5\n\tgithub.com/robfig/cron/v3 v3.0.1\n\tgithub.com/stretchr/testify v1.8.4\n\tgithub.com/urfave/cli v1.22.5\n\tgo.mongodb.org/mongo-driver v1.17.0\n\tgopkg.in/yaml.v2 v2.4.0\n)\n\nrequire (\n\tcloud.google.com/go v0.76.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect\n\tgithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/golang/snappy v0.0.4 // indirect\n\tgithub.com/google/go-cmp v0.6.0 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.0.5 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/jmespath/go-jmespath v0.4.0 // indirect\n\tgithub.com/jstemmer/go-junit-report v0.9.1 // indirect\n\tgithub.com/klauspost/compress v1.13.6 // indirect\n\tgithub.com/montanaflynn/stats v0.7.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/scram v1.1.2 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.4 // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect\n\tgo.opencensus.io v0.22.6 // indirect\n\tgolang.org/x/crypto v0.26.0 // indirect\n\tgolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect\n\tgolang.org/x/mod v0.17.0 // indirect\n\tgolang.org/x/net v0.25.0 // indirect\n\tgolang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c // indirect\n\tgolang.org/x/sync v0.8.0 // indirect\n\tgolang.org/x/sys v0.23.0 // indirect\n\tgolang.org/x/text v0.17.0 // indirect\n\tgolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect\n\tgoogle.golang.org/api v0.39.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea // indirect\n\tgoogle.golang.org/grpc v1.35.0 // indirect\n\tgoogle.golang.org/protobuf v1.26.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\nreplace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=\ncloud.google.com/go v0.76.0 h1:Ckw+E/QYZgd/5bpI4wz4h6f+jmpvh9S9uSrKNnbicJI=\ncloud.google.com/go v0.76.0/go.mod h1:r9EvIAvLrunusnetGdQ50M/gKui1x3zdGW/VELGkdpw=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/pubsub v1.10.0 h1:JK22g5uNpscGPthjJE/D0siWtA6UlU4Cb6pLcyJkzyQ=\ncloud.google.com/go/pubsub v1.10.0/go.mod h1:eNpTrkOy7dCpkNyaSNetMa6udbgecJMd0ZsTJS/cuNo=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae h1:DcFpTQBYQ9Ct2d6sC7ol0/ynxc2pO1cpGUM+f4t5adg=\ngithub.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae/go.mod h1:rJJ84PyA/Wlmw1hO+xTzV2wsSUon6J5ktg0g8BF2PuU=\ngithub.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=\ngithub.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=\ngithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=\ngithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=\ngithub.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=\ngithub.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=\ngithub.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=\ngithub.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=\ngithub.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=\ngithub.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=\ngithub.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=\ngithub.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=\ngithub.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=\ngithub.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=\ngithub.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=\ngithub.com/go-redsync/redsync/v4 v4.8.1 h1:rq2RvdTI0obznMdxKUWGdmmulo7lS9yCzb8fgDKOlbM=\ngithub.com/go-redsync/redsync/v4 v4.8.1/go.mod h1:LmUAsQuQxhzZAoGY7JS6+dNhNmZyonMZiiEDY9plotM=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=\ngithub.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=\ngithub.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=\ngithub.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=\ngithub.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=\ngithub.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=\ngithub.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=\ngithub.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=\ngithub.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=\ngithub.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=\ngithub.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=\ngithub.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=\ngithub.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=\ngithub.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=\ngithub.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=\ngithub.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=\ngithub.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=\ngithub.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=\ngithub.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r40k=\ngo.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok=\ngo.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=\ngo.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=\ngolang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c h1:HiAZXo96zOhVhtFHchj/ojzoxCFiPrp9/j0GtS38V3g=\ngolang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=\ngolang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=\ngolang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=\ngolang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.38.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.39.0 h1:zHCTXf0NeDdKTgcSQpT+ZflWAqHsEp1GmdpxW09f3YM=\ngoogle.golang.org/api v0.39.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210202153253-cf70463f6119/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea h1:N98SvVh7Hdle2lgUVFuIkf0B3u29CUakMUQa7Hwz8Wc=\ngoogle.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "instruction-notes/dynamodb.md",
    "content": "# Using DynamoDB as a result backend\n## What is DynamoDB\nAmazon DynamoDB is a fast and flexible NoSQL database service.\nCheck this [official website](https://aws.amazon.com/dynamodb/\n) for details.\n\n## How to use DynamoDB as a result backend in Machinery\n### Create two tables first\nThere will be two tables required now(2018-01-12):\n* group_metas: A table which saves group tasks' meta data. The primary key for this table is ```GroupUUID```, and it should be set properly when creating this table.\n* task_states: A table which saves every task's states. The primary key for this table is ```TaskUUID```, and it should be set properly when creating this table.\n\n\n### Add DynamoDB config to the config file\n#### example config\n```yaml\nbroker: 'https://sqs.us-west-1.amazonaws.com/123456789012'\ndefault_queue: machinery-queue\nresult_backend: 'https://dynamodb.us-west-1.amazonaws.com/123456789012'\nresults_expire_in: 3600\ndynamodb:\n  task_states_table: 'task_states'\n  group_metas_table: 'group_metas'\n```\nThen DynamoDB will be used as a result backend."
  },
  {
    "path": "integration-tests/amqp_amqp_test.go",
    "content": "package integration_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestAmqpAmqp(t *testing.T) {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tif amqpURL == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tfinalAmqpURL := amqpURL\n\tvar finalSeparator string\n\n\tamqpURLs := os.Getenv(\"AMQP_URLS\")\n\tif amqpURLs != \"\" {\n\t\tseparator := os.Getenv(\"AMQP_URLS_SEPARATOR\")\n\t\tif separator == \"\" {\n\t\t\treturn\n\t\t}\n\t\tfinalSeparator = separator\n\t\tfinalAmqpURL = amqpURLs\n\t}\n\n\t// AMQP broker, AMQP result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:                  finalAmqpURL,\n\t\tMultipleBrokerSeparator: finalSeparator,\n\t\tDefaultQueue:            \"test_queue\",\n\t\tResultBackend:           amqpURL,\n\t\tLock:                    \"eager\",\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/amqp_get_pending_tasks_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/backends/result\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\nfunc TestAmqpGetPendingTasks(t *testing.T) {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tif amqpURL == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tfinalAmqpURL := amqpURL\n\tvar finalSeparator string\n\n\tamqpURLs := os.Getenv(\"AMQP_URLS\")\n\tif amqpURLs != \"\" {\n\t\tseparator := os.Getenv(\"AMQP_URLS_SEPARATOR\")\n\t\tif separator == \"\" {\n\t\t\treturn\n\t\t}\n\t\tfinalSeparator = separator\n\t\tfinalAmqpURL = amqpURLs\n\t}\n\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\t// AMQP broker, AMQP result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:                  finalAmqpURL,\n\t\tMultipleBrokerSeparator: finalSeparator,\n\t\tDefaultQueue:            \"test_queue\",\n\t\tResultBackend:           amqpURL,\n\t\tLock:                    fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t})\n\n\tvar results []*result.AsyncResult\n\tsignatures := []*tasks.Signature{newAddTask(1, 2), newAddTask(3, 5), newAddTask(6, 7)}\n\tfor _, s := range signatures {\n\t\tar, err := server.SendTask(s)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tresults = append(results, ar)\n\t}\n\tpendingMessages, err := server.GetBroker().GetPendingTasks(server.GetConfig().DefaultQueue)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(pendingMessages) != len(signatures) {\n\t\tt.Errorf(\n\t\t\t\"%d pending messages, should be %d\",\n\t\t\tlen(pendingMessages),\n\t\t\tlen(signatures),\n\t\t)\n\t}\n\tfor i := 0; i < len(signatures); i++ {\n\t\tcompareSigs(t, signatures[i], pendingMessages[i])\n\t}\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tgo worker.Launch()\n\tdefer worker.Quit()\n\tfor _, r := range results {\n\t\tr.Get(time.Duration(time.Millisecond * 5))\n\t}\n\n\tpendingMessages, err = server.GetBroker().GetPendingTasks(server.GetConfig().DefaultQueue)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(pendingMessages) != 0 {\n\t\tt.Errorf(\n\t\t\t\"%d pending messages, should be 0\",\n\t\t\tlen(pendingMessages),\n\t\t)\n\t}\n}\n\nfunc compareSigs(t *testing.T, a *tasks.Signature, b *tasks.Signature) {\n\tif a.UUID != b.UUID {\n\t\tt.Errorf(\"UUID mismatch, %v != %v\", a.UUID, b.UUID)\n\t}\n\tif a.Name != b.Name {\n\t\tt.Errorf(\"UUID mismatch, %v != %v\", a.Name, b.Name)\n\t}\n\tif len(a.Args) != len(b.Args) {\n\t\tt.Errorf(\"Arg length mismatch, %v != %v\", len(a.Args), len(b.Args))\n\t}\n}\n"
  },
  {
    "path": "integration-tests/amqp_memcache_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestAmqpMemcache(t *testing.T) {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tmemcacheURL := os.Getenv(\"MEMCACHE_URL\")\n\tif amqpURL == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\tif memcacheURL == \"\" {\n\t\tt.Skip(\"MEMCACHE_URL is not defined\")\n\t}\n\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\t// AMQP broker, Memcache result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        amqpURL,\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"memcache://%v\", memcacheURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/amqp_mongodb_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestAmqpMongodb(t *testing.T) {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tmongodbURL := os.Getenv(\"MONGODB_URL\")\n\tif amqpURL == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\tif mongodbURL == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\t// AMQP broker, MongoDB result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:          amqpURL,\n\t\tDefaultQueue:    \"test_queue\",\n\t\tResultsExpireIn: 30,\n\t\tResultBackend:   fmt.Sprintf(\"mongodb://%v\", mongodbURL),\n\t\tLock:            \"eager\",\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/amqp_redis_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestAmqpRedis(t *testing.T) {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif amqpURL == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\t// AMQP broker, Redis result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        amqpURL,\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          \"eager\",\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/eager_eager_test.go",
    "content": "package integration_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype EagerIntegrationTestSuite struct {\n\tsuite.Suite\n\n\tsrv    *machinery.Server\n\tcalled float64\n}\n\nfunc TestEagerIntegrationTestSuite(t *testing.T) {\n\tsuite.Run(t, &EagerIntegrationTestSuite{})\n}\n\nfunc (s *EagerIntegrationTestSuite) SetupSuite() {\n\tvar err error\n\n\t// init server\n\tcnf := config.Config{\n\t\tBroker:        \"eager\",\n\t\tResultBackend: \"eager\",\n\t\tLock:          \"eager\",\n\t}\n\ts.srv, err = machinery.NewServer(&cnf)\n\ts.Nil(err)\n\ts.NotNil(s.srv)\n\n\t// register task\n\ts.called = 0\n\ts.srv.RegisterTask(\"float_called\", func(i float64) (float64, error) {\n\t\ts.called = i\n\t\treturn s.called, nil\n\t})\n\n\ts.srv.RegisterTask(\"float_result\", func(i float64) (float64, error) {\n\t\treturn i + 100.0, nil\n\t})\n\n\ts.srv.RegisterTask(\"int_result\", func(i int64) (int64, error) {\n\t\treturn i + 100, nil\n\t})\n}\n\nfunc (s *EagerIntegrationTestSuite) TestCalled() {\n\t_, err := s.srv.SendTask(&tasks.Signature{\n\t\tName: \"float_called\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: 100.0,\n\t\t\t},\n\t\t},\n\t})\n\n\ts.Nil(err)\n\ts.Equal(100.0, s.called)\n}\n\nfunc (s *EagerIntegrationTestSuite) TestSuccessResult() {\n\t// float64\n\t{\n\t\tasyncResult, err := s.srv.SendTask(&tasks.Signature{\n\t\t\tName: \"float_result\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"float64\",\n\t\t\t\t\tValue: 100.0,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\ts.NotNil(asyncResult)\n\t\ts.Nil(err)\n\n\t\ts.True(asyncResult.GetState().IsCompleted())\n\t\ts.True(asyncResult.GetState().IsSuccess())\n\n\t\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\t\tif s.NoError(err) {\n\t\t\tif len(results) != 1 {\n\t\t\t\ts.T().Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t\t\t}\n\n\t\t\ts.Equal(reflect.Float64, results[0].Kind())\n\t\t\tif results[0].Kind() == reflect.Float64 {\n\t\t\t\ts.Equal(200.0, results[0].Float())\n\t\t\t}\n\t\t}\n\t}\n\n\t// int\n\t{\n\t\tasyncResult, err := s.srv.SendTask(&tasks.Signature{\n\t\t\tName: \"int_result\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 100,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\ts.NotNil(asyncResult)\n\t\ts.Nil(err)\n\n\t\ts.True(asyncResult.GetState().IsCompleted())\n\t\ts.True(asyncResult.GetState().IsSuccess())\n\n\t\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\t\tif s.NoError(err) {\n\t\t\tif len(results) != 1 {\n\t\t\t\ts.T().Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t\t\t}\n\n\t\t\ts.Equal(reflect.Int64, results[0].Kind())\n\t\t\tif results[0].Kind() == reflect.Int64 {\n\t\t\t\ts.Equal(int64(200), results[0].Int())\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "integration-tests/gcppubsub_redis_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"cloud.google.com/go/pubsub\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc createGCPPubSubTopicAndSubscription(cli *pubsub.Client, topicName, subscriptionName string) {\n\tctx := context.Background()\n\n\tvar topic *pubsub.Topic\n\n\ttopic = cli.Topic(topicName)\n\ttopicExists, err := topic.Exists(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tif !topicExists {\n\t\ttopic, err = cli.CreateTopic(ctx, topicName)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\tvar sub *pubsub.Subscription\n\n\tsub = cli.Subscription(subscriptionName)\n\tsubExists, err := sub.Exists(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tif !subExists {\n\t\t_, err = cli.CreateSubscription(ctx, subscriptionName, pubsub.SubscriptionConfig{\n\t\t\tTopic:       topic,\n\t\t\tAckDeadline: 10 * time.Second,\n\t\t})\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n\nfunc TestGCPPubSubRedis(t *testing.T) {\n\t// start Cloud Pub/Sub emulator\n\t// $ LANG=C gcloud beta emulators pubsub start\n\t// $ eval $(LANG=C gcloud beta emulators pubsub env-init)\n\n\tpubsubURL := os.Getenv(\"GCPPUBSUB_URL\")\n\tif pubsubURL == \"\" {\n\t\tt.Skip(\"GCPPUBSUB_URL is not defined\")\n\t}\n\n\ttopicName := os.Getenv(\"GCPPUBSUB_TOPIC\")\n\tif topicName == \"\" {\n\t\tt.Skip(\"GCPPUBSUB_TOPIC is not defined\")\n\t}\n\n\t_, subscriptionName, err := machinery.ParseGCPPubSubURL(pubsubURL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\tpubsubClient, err := pubsub.NewClient(context.Background(), \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create Cloud Pub/Sub Topic and Subscription\n\tcreateGCPPubSubTopicAndSubscription(pubsubClient, topicName, subscriptionName)\n\n\t// Redis broker, Redis result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        pubsubURL,\n\t\tDefaultQueue:  topicName,\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tGCPPubSub: &config.GCPPubSubConfig{\n\t\t\tClient: pubsubClient,\n\t\t},\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/redis_get_pending_tasks_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestRedisGetPendingTasks(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\t// Redis broker, Redis result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t})\n\tpendingMessages, err := server.GetBroker().GetPendingTasks(server.GetConfig().DefaultQueue)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif len(pendingMessages) != 0 {\n\t\tt.Errorf(\n\t\t\t\"%d pending messages, should be %d\",\n\t\t\tlen(pendingMessages),\n\t\t\t0,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "integration-tests/redis_memcache_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestRedisMemcache(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tmemcacheURL := os.Getenv(\"MEMCACHE_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\tif memcacheURL == \"\" {\n\t\tt.Skip(\"MEMCACHE_URL is not defined\")\n\t}\n\n\t// Redis broker, Redis result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"memcache://%v\", memcacheURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/redis_mongodb_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestRedisMongodb(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tmongodbURL := os.Getenv(\"MONGODB_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\tif mongodbURL == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\t// Redis broker, MongoDB result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tDefaultQueue:    \"test_queue\",\n\t\tResultsExpireIn: 30,\n\t\tResultBackend:   fmt.Sprintf(\"mongodb://%v\", mongodbURL),\n\t\tLock:            fmt.Sprintf(\"redis://%v\", redisURL),\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/redis_redis_test.go",
    "content": "package integration_test\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestRedisRedis_Redigo(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\t// Redis broker, Redis result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n\nfunc TestRedisRedisNormalTaskPollPeriodLessThan1SecondShouldNotFailNextTask(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\t// Redis broker, Redis result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tRedis: &config.RedisConfig{\n\t\t\tNormalTasksPollPeriod: 10, // 10 milliseconds\n\t\t},\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tgo worker.Launch()\n\tdefer worker.Quit()\n\ttestSendTask(server, t)\n}\n\nfunc TestRedisRedisWorkerQuitRaceCondition(t *testing.T) {\n\trepeat := 3\n\tfor i := 0; i < repeat; i++ {\n\t\tredisURL := os.Getenv(\"REDIS_URL\")\n\t\tif redisURL == \"\" {\n\t\t\tt.Skip(\"REDIS_URL is not defined\")\n\t\t}\n\n\t\t// Redis broker, Redis result backend\n\t\tcnf := &config.Config{\n\t\t\tBroker:        fmt.Sprintf(\"redis://%v\", redisURL),\n\t\t\tDefaultQueue:  \"test_queue\",\n\t\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t\t}\n\n\t\tserver, _ := machinery.NewServer(cnf)\n\t\tworker := server.NewWorker(\"test_worker\", 0)\n\n\t\terrorsChan := make(chan error, 1)\n\n\t\t// Check Quit() immediately after LaunchAsync() will shutdown gracefully\n\t\t// and not panic on close(b.stopChan)\n\t\tworker.LaunchAsync(errorsChan)\n\t\tworker.Quit()\n\n\t\tif err := <-errorsChan; err != nil {\n\t\t\tt.Errorf(\"Error shutting down machinery worker gracefully %+v\", err)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc TestRedisRedisWorkerQuickQuit(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\t// Redis broker, Redis result backend\n\tpollPeriod := 1\n\tcnf := &config.Config{\n\t\tBroker:        fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tRedis: &config.RedisConfig{\n\t\t\tNormalTasksPollPeriod: pollPeriod, // default: 1000\n\t\t},\n\t}\n\n\tserver, _ := machinery.NewServer(cnf)\n\tworker := server.NewWorker(\"test_worker\", 0)\n\n\terrorsChan := make(chan error, 1)\n\n\t// Check Quit() immediately after LaunchAsync() will shutdown gracefully\n\t// and not panic\n\tworker.LaunchAsync(errorsChan)\n\n\tbefore := time.Now()\n\tworker.Quit()\n\tdelta := time.Since(before)\n\n\tthreshold := time.Duration(pollPeriod)*time.Millisecond + 1000 // add 1 second as buffer\n\n\tif delta.Nanoseconds() > threshold.Nanoseconds() {\n\t\tt.Error(\"Worker quit() exceeded timeout\")\n\t}\n}\n\nfunc TestRedisRedisWorkerPreConsumeHandler(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\t// Redis broker, Redis result backend\n\tpollPeriod := 1\n\tcnf := &config.Config{\n\t\tBroker:        fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tRedis: &config.RedisConfig{\n\t\t\tNormalTasksPollPeriod: pollPeriod, // default: 1000\n\t\t},\n\t}\n\n\tserver, _ := machinery.NewServer(cnf)\n\tworker := server.NewWorker(\"test_worker\", 0)\n\terrorsChan := make(chan error)\n\terr := errors.New(\"PreConsumeHandler is invoked\")\n\tworker.SetPreConsumeHandler(func(*machinery.Worker) bool {\n\t\terrorsChan <- err\n\t\treturn true\n\t})\n\n\tworker.LaunchAsync(errorsChan)\n\tif err != <-errorsChan {\n\t\tt.Error(\"PreConsumeHandler was not invoked\")\n\t}\n}\n"
  },
  {
    "path": "integration-tests/redis_socket_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestRedisSocket(t *testing.T) {\n\tredisSocket := os.Getenv(\"REDIS_SOCKET\")\n\tif redisSocket == \"\" {\n\t\tt.Skip(\"REDIS_SOCKET is not defined\")\n\t}\n\n\t// Redis broker, Redis result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        fmt.Sprintf(\"redis+socket://%v\", redisSocket),\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis+socket://%v\", redisSocket),\n\t\tLock:          \"eager\",\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/sqs_amqp_test.go",
    "content": "package integration_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestSQSAmqp(t *testing.T) {\n\tsqsURL := os.Getenv(\"SQS_URL\")\n\tif sqsURL == \"\" {\n\t\tt.Skip(\"SQS_URL is not defined\")\n\t}\n\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tif amqpURL == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\t// AMQP broker, AMQP result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:        sqsURL,\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: amqpURL,\n\t\tLock:          \"eager\",\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t})\n\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "integration-tests/sqs_mongodb_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestSQSMongodb(t *testing.T) {\n\tsqsURL := os.Getenv(\"SQS_URL\")\n\tmongodbURL := os.Getenv(\"MONGODB_URL\")\n\tif sqsURL == \"\" {\n\t\tt.Skip(\"SQS_URL is not defined\")\n\t}\n\tif mongodbURL == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\t// AMQP broker, MongoDB result backend\n\tserver := testSetup(&config.Config{\n\t\tBroker:          sqsURL,\n\t\tDefaultQueue:    \"test_queue\",\n\t\tResultsExpireIn: 30,\n\t\tResultBackend:   fmt.Sprintf(\"mongodb://%v\", mongodbURL),\n\t\tLock:            \"eager\",\n\t})\n\tworker := server.(*machinery.Server).NewWorker(\"test_worker\", 0)\n\tgo worker.Launch()\n\ttestAll(server, t)\n\tworker.Quit()\n}\n"
  },
  {
    "path": "integration-tests/suite_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"log\"\n\t\"reflect\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/backends/result\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\n\tbrokersiface \"github.com/RichardKnop/machinery/v1/brokers/iface\"\n)\n\ntype ascendingInt64s []int64\n\nfunc (a ascendingInt64s) Len() int           { return len(a) }\nfunc (a ascendingInt64s) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }\nfunc (a ascendingInt64s) Less(i, j int) bool { return a[i] < a[j] }\n\ntype Server interface {\n\tGetBroker() brokersiface.Broker\n\tGetConfig() *config.Config\n\tRegisterTasks(namedTaskFuncs map[string]interface{}) error\n\tSendTaskWithContext(ctx context.Context, signature *tasks.Signature) (*result.AsyncResult, error)\n\tSendTask(signature *tasks.Signature) (*result.AsyncResult, error)\n\tSendChainWithContext(ctx context.Context, chain *tasks.Chain) (*result.ChainAsyncResult, error)\n\tSendChain(chain *tasks.Chain) (*result.ChainAsyncResult, error)\n\tSendGroupWithContext(ctx context.Context, group *tasks.Group, sendConcurrency int) ([]*result.AsyncResult, error)\n\tSendGroup(group *tasks.Group, sendConcurrency int) ([]*result.AsyncResult, error)\n\tSendChordWithContext(ctx context.Context, chord *tasks.Chord, sendConcurrency int) (*result.ChordAsyncResult, error)\n\tSendChord(chord *tasks.Chord, sendConcurrency int) (*result.ChordAsyncResult, error)\n}\n\nfunc testAll(server Server, t *testing.T) {\n\ttestSendTask(server, t)\n\ttestSendGroup(server, t, 0) // with unlimited concurrency\n\ttestSendGroup(server, t, 2) // with limited concurrency (2 parallel tasks at the most)\n\ttestSendChord(server, t)\n\ttestSendChain(server, t)\n\ttestReturnJustError(server, t)\n\ttestReturnMultipleValues(server, t)\n\ttestPanic(server, t)\n\ttestDelay(server, t)\n}\n\nfunc testSendTask(server Server, t *testing.T) {\n\taddTask := newAddTask(1, 1)\n\n\tasyncResult, err := server.SendTask(addTask)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\tif results[0].Interface() != int64(2) {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want int64(2)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n\n\tsumTask := newSumTask([]int64{1, 2})\n\tasyncResult, err = server.SendTask(sumTask)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\tif results[0].Interface() != int64(3) {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want int64(3)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n}\n\nfunc testSendGroup(server Server, t *testing.T, sendConcurrency int) {\n\tt1, t2, t3 := newAddTask(1, 1), newAddTask(2, 2), newAddTask(5, 6)\n\n\tgroup, err := tasks.NewGroup(t1, t2, t3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tasyncResults, err := server.SendGroup(group, sendConcurrency)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\texpectedResults := []int64{2, 4, 11}\n\n\tactualResults := make([]int64, 3)\n\n\tfor i, asyncResult := range asyncResults {\n\t\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif len(results) != 1 {\n\t\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t\t}\n\n\t\tintResult, ok := results[0].Interface().(int64)\n\t\tif !ok {\n\t\t\tt.Errorf(\"Could not convert %v to int64\", results[0].Interface())\n\t\t}\n\t\tactualResults[i] = intResult\n\t}\n\n\tsort.Sort(ascendingInt64s(actualResults))\n\n\tif !reflect.DeepEqual(expectedResults, actualResults) {\n\t\tt.Errorf(\n\t\t\t\"expected results = %v, actual results = %v\",\n\t\t\texpectedResults,\n\t\t\tactualResults,\n\t\t)\n\t}\n}\n\nfunc testSendChain(server Server, t *testing.T) {\n\tt1, t2, t3 := newAddTask(2, 2), newAddTask(5, 6), newMultipleTask(4)\n\n\tchain, err := tasks.NewChain(t1, t2, t3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tchainAsyncResult, err := server.SendChain(chain)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := chainAsyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\tif results[0].Interface() != int64(60) {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want int64(60)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n}\n\nfunc testSendChord(server Server, t *testing.T) {\n\tt1, t2, t3, t4 := newAddTask(1, 1), newAddTask(2, 2), newAddTask(5, 6), newMultipleTask()\n\n\tgroup, err := tasks.NewGroup(t1, t2, t3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tchord, err := tasks.NewChord(group, t4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tchordAsyncResult, err := server.SendChord(chord, 10)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := chordAsyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\tif results[0].Interface() != int64(88) {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want int64(88)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n}\n\nfunc testReturnJustError(server Server, t *testing.T) {\n\t// Fails, returns error as the only value\n\ttask := newErrorTask(\"Test error\", true)\n\tasyncResult, err := server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif len(results) != 0 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 0)\n\t}\n\tassert.Equal(t, \"Test error\", err.Error())\n\n\t// Successful, returns nil as the only value\n\ttask = newErrorTask(\"\", false)\n\tasyncResult, err = server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif len(results) != 0 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 0)\n\t}\n\tassert.NoError(t, err)\n}\n\nfunc testReturnMultipleValues(server Server, t *testing.T) {\n\t// Successful task with multiple return values\n\ttask := newMultipleReturnTask(\"foo\", \"bar\", false)\n\n\tasyncResult, err := server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 2 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 2)\n\t}\n\n\tif results[0].Interface() != \"foo\" {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want string(\\\"foo\\\":)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n\n\tif results[1].Interface() != \"bar\" {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want string(\\\"bar\\\":)\",\n\t\t\tresults[1].Type().String(),\n\t\t\tresults[1].Interface(),\n\t\t)\n\t}\n\n\t// Failed task with multiple return values\n\ttask = newMultipleReturnTask(\"\", \"\", true)\n\n\tasyncResult, err = server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif len(results) != 0 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 0)\n\t}\n\tassert.Error(t, err)\n}\n\nfunc testPanic(server Server, t *testing.T) {\n\ttask := &tasks.Signature{Name: \"panic\"}\n\tasyncResult, err := server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif len(results) != 0 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 0)\n\t}\n\tassert.Equal(t, \"oops\", err.Error())\n}\n\nfunc testDelay(server Server, t *testing.T) {\n\tnow := time.Now().UTC()\n\teta := now.Add(100 * time.Millisecond)\n\ttask := newDelayTask(eta)\n\tasyncResult, err := server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(5 * time.Millisecond))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\ttm, ok := results[0].Interface().(int64)\n\tif !ok {\n\t\tt.Errorf(\n\t\t\t\"Could not type assert = %v(%v) to int64\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n\n\tif tm < eta.UnixNano() {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want >= int64(%d)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t\teta.UnixNano(),\n\t\t)\n\t}\n}\n\nfunc registerTestTasks(server Server) {\n\n\ttasks := map[string]interface{}{\n\t\t\"add\": func(args ...int64) (int64, error) {\n\t\t\tsum := int64(0)\n\t\t\tfor _, arg := range args {\n\t\t\t\tsum += arg\n\t\t\t}\n\t\t\treturn sum, nil\n\t\t},\n\t\t\"multiply\": func(args ...int64) (int64, error) {\n\t\t\tsum := int64(1)\n\t\t\tfor _, arg := range args {\n\t\t\t\tsum *= arg\n\t\t\t}\n\t\t\treturn sum, nil\n\t\t},\n\t\t\"sum\": func(numbers []int64) (int64, error) {\n\t\t\tvar sum int64\n\t\t\tfor _, num := range numbers {\n\t\t\t\tsum += num\n\t\t\t}\n\t\t\treturn sum, nil\n\t\t},\n\t\t\"return_just_error\": func(msg string, fail bool) (err error) {\n\t\t\tif fail {\n\t\t\t\terr = errors.New(msg)\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\t\"return_multiple_values\": func(arg1, arg2 string, fail bool) (r1 string, r2 string, err error) {\n\t\t\tif fail {\n\t\t\t\terr = errors.New(\"some error\")\n\t\t\t} else {\n\t\t\t\tr1 = arg1\n\t\t\t\tr2 = arg2\n\t\t\t}\n\t\t\treturn r1, r2, err\n\t\t},\n\t\t\"panic\": func() (string, error) {\n\t\t\tpanic(errors.New(\"oops\"))\n\t\t},\n\t\t\"delay_test\": func() (int64, error) {\n\t\t\treturn time.Now().UTC().UnixNano(), nil\n\t\t},\n\t}\n\n\tserver.RegisterTasks(tasks)\n}\n\nfunc testSetup(cnf *config.Config) Server {\n\n\tserver, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\tlog.Fatal(err, \"Could not initialize server\")\n\t}\n\n\tregisterTestTasks(server)\n\n\treturn server\n}\n\nfunc newAddTask(a, b int) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"add\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: a,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: b,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc newMultipleTask(nums ...int) *tasks.Signature {\n\targs := make([]tasks.Arg, len(nums))\n\tfor i, n := range nums {\n\t\targs[i] = tasks.Arg{\n\t\t\tType:  \"int64\",\n\t\t\tValue: n,\n\t\t}\n\t}\n\treturn &tasks.Signature{\n\t\tName: \"multiply\",\n\t\tArgs: args,\n\t}\n}\n\nfunc newSumTask(nums []int64) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"sum\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"[]int64\",\n\t\t\t\tValue: nums,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc newErrorTask(msg string, fail bool) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"return_just_error\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"string\",\n\t\t\t\tValue: msg,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"bool\",\n\t\t\t\tValue: fail,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc newMultipleReturnTask(arg1, arg2 string, fail bool) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"return_multiple_values\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"string\",\n\t\t\t\tValue: arg1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"string\",\n\t\t\t\tValue: arg2,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"bool\",\n\t\t\t\tValue: fail,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc newDelayTask(eta time.Time) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"delay_test\",\n\t\tETA:  &eta,\n\t}\n}\n"
  },
  {
    "path": "integration-tests/worker_only_consumes_registered_tasks_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\nfunc TestWorkerOnlyConsumesRegisteredTaskAMQP(t *testing.T) {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tif amqpURL == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tcnf := config.Config{\n\t\tBroker:        amqpURL,\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: amqpURL,\n\t\tLock:          \"eager\",\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 3,\n\t\t},\n\t}\n\n\tserver1, err := machinery.NewServer(&cnf)\n\tif err != nil {\n\t\tt.Fatal(err, \"Could not initialize server\")\n\t}\n\n\tserver1.RegisterTask(\"add\", func(args ...int64) (int64, error) {\n\t\tsum := int64(0)\n\t\tfor _, arg := range args {\n\t\t\tsum += arg\n\t\t}\n\t\treturn sum, nil\n\t})\n\n\tserver2, err := machinery.NewServer(&cnf)\n\tif err != nil {\n\t\tt.Fatal(err, \"Could not initialize server\")\n\t}\n\n\tserver2.RegisterTask(\"multiply\", func(args ...int64) (int64, error) {\n\t\tsum := int64(1)\n\t\tfor _, arg := range args {\n\t\t\tsum *= arg\n\t\t}\n\t\treturn sum, nil\n\t})\n\n\ttask1 := tasks.Signature{\n\t\tName: \"add\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 2,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 3,\n\t\t\t},\n\t\t},\n\t}\n\n\ttask2 := tasks.Signature{\n\t\tName: \"multiply\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 4,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 5,\n\t\t\t},\n\t\t},\n\t}\n\n\tworker1 := server1.NewWorker(\"test_worker\", 0)\n\tworker2 := server2.NewWorker(\"test_worker2\", 0)\n\tgo worker1.Launch()\n\tgo worker2.Launch()\n\n\tgroup, err := tasks.NewGroup(&task2, &task1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tasyncResults, err := server1.SendGroup(group, 10)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\texpectedResults := []int64{5, 20}\n\tactualResults := make([]int64, 2)\n\n\tfor i, asyncResult := range asyncResults {\n\t\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif len(results) != 1 {\n\t\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t\t}\n\n\t\tintResult, ok := results[0].Interface().(int64)\n\t\tif !ok {\n\t\t\tt.Errorf(\"Could not convert %v to int64\", results[0].Interface())\n\t\t}\n\t\tactualResults[i] = intResult\n\t}\n\n\tworker1.Quit()\n\tworker2.Quit()\n\n\tsort.Sort(ascendingInt64s(actualResults))\n\n\tif !reflect.DeepEqual(expectedResults, actualResults) {\n\t\tt.Errorf(\n\t\t\t\"expected results = %v, actual results = %v\",\n\t\t\texpectedResults,\n\t\t\tactualResults,\n\t\t)\n\t}\n}\n\nfunc TestWorkerOnlyConsumesRegisteredTaskRedis(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\tcnf := config.Config{\n\t\tBroker:        fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t}\n\n\tserver1, err := machinery.NewServer(&cnf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tserver1.RegisterTask(\"add\", func(args ...int64) (int64, error) {\n\t\tsum := int64(0)\n\t\tfor _, arg := range args {\n\t\t\tsum += arg\n\t\t}\n\t\treturn sum, nil\n\t})\n\n\tserver2, err := machinery.NewServer(&cnf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tserver2.RegisterTask(\"multiply\", func(args ...int64) (int64, error) {\n\t\tsum := int64(1)\n\t\tfor _, arg := range args {\n\t\t\tsum *= arg\n\t\t}\n\t\treturn sum, nil\n\t})\n\n\ttask1 := tasks.Signature{\n\t\tName: \"add\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 2,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 3,\n\t\t\t},\n\t\t},\n\t}\n\n\ttask2 := tasks.Signature{\n\t\tName: \"multiply\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 4,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 5,\n\t\t\t},\n\t\t},\n\t}\n\n\tworker1 := server1.NewWorker(\"test_worker\", 0)\n\tworker2 := server2.NewWorker(\"test_worker2\", 0)\n\tgo worker1.Launch()\n\tgo worker2.Launch()\n\n\tgroup, err := tasks.NewGroup(&task2, &task1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tasyncResults, err := server1.SendGroup(group, 10)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\texpectedResults := []int64{5, 20}\n\tactualResults := make([]int64, 2)\n\n\tfor i, asyncResult := range asyncResults {\n\t\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif len(results) != 1 {\n\t\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t\t}\n\n\t\tintResult, ok := results[0].Interface().(int64)\n\t\tif !ok {\n\t\t\tt.Errorf(\"Could not convert %v to int64\", results[0].Interface())\n\t\t}\n\t\tactualResults[i] = intResult\n\t}\n\n\tworker1.Quit()\n\tworker2.Quit()\n\n\tsort.Sort(ascendingInt64s(actualResults))\n\n\tif !reflect.DeepEqual(expectedResults, actualResults) {\n\t\tt.Errorf(\n\t\t\t\"expected results = %v, actual results = %v\",\n\t\t\texpectedResults,\n\t\t\tactualResults,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "v1/backends/amqp/amqp.go",
    "content": "package amqp\n\n// NOTE: Using AMQP as a result backend is quite tricky since every time we\n// read a message from the queue keeping task states, the message is removed\n// from the queue. This leads to problems with keeping a reliable state of a\n// group of tasks since concurrent processes updating the group state cause\n// race conditions and inconsistent state.\n//\n// This is avoided by a \"clever\" hack. A special queue identified by a group\n// UUID is created and we store serialised TaskState objects of successfully\n// completed tasks. By inspecting the queue we can then say:\n// 1) If all group tasks finished (number of unacked messages = group task count)\n// 2) If all group tasks finished AND succeeded (by consuming the queue)\n//\n// It is important to consume the queue exclusively to avoid race conditions.\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\n// Backend represents an AMQP result backend\ntype Backend struct {\n\tcommon.Backend\n\tcommon.AMQPConnector\n}\n\n// New creates Backend instance\nfunc New(cnf *config.Config) iface.Backend {\n\treturn &Backend{Backend: common.NewBackend(cnf), AMQPConnector: common.AMQPConnector{}}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\treturn nil\n}\n\n// GroupCompleted returns true if all tasks in a group finished\n// NOTE: Given AMQP limitation this will only return true if all finished\n// tasks were successful as we do not keep track of completed failed tasks\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tqueueState, err := b.InspectQueue(channel, groupUUID)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\treturn queueState.Messages == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tqueueState, err := b.InspectQueue(channel, groupUUID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif queueState.Messages != groupTaskCount {\n\t\treturn nil, fmt.Errorf(\"Already consumed: %v\", err)\n\t}\n\n\tdeliveries, err := channel.Consume(\n\t\tgroupUUID, // queue name\n\t\t\"\",        // consumer tag\n\t\tfalse,     // auto-ack\n\t\ttrue,      // exclusive\n\t\tfalse,     // no-local\n\t\tfalse,     // no-wait\n\t\tnil,       // arguments\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Queue consume error: %s\", err)\n\t}\n\n\tstates := make([]*tasks.TaskState, groupTaskCount)\n\tfor i := 0; i < groupTaskCount; i++ {\n\t\td := <-deliveries\n\n\t\tstate := new(tasks.TaskState)\n\t\tdecoder := json.NewDecoder(bytes.NewReader([]byte(d.Body)))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(state); err != nil {\n\t\t\td.Nack(false, false) // multiple, requeue\n\t\t\treturn nil, err\n\t\t}\n\n\t\td.Ack(false) // multiple\n\n\t\tstates[i] = state\n\t}\n\n\treturn states, nil\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer b.Close(channel, conn)\n\n\t_, err = b.InspectQueue(channel, amqmChordTriggeredQueue(groupUUID))\n\tif err != nil {\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\ttaskState := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\ttaskState := tasks.NewStartedTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tstate := tasks.NewRetryTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\n\tif err := b.updateState(taskState); err != nil {\n\t\treturn err\n\t}\n\n\tif signature.GroupUUID == \"\" {\n\t\treturn nil\n\t}\n\n\treturn b.markTaskCompleted(signature, taskState)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\n\tif err := b.updateState(taskState); err != nil {\n\t\treturn err\n\t}\n\n\tif signature.GroupUUID == \"\" {\n\t\treturn nil\n\t}\n\n\treturn b.markTaskCompleted(signature, taskState)\n}\n\n// GetState returns the latest task state. It will only return the status once\n// as the message will get consumed and removed from the queue.\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\tdeclareQueueArgs := amqp.Table{\n\t\t// Time in milliseconds\n\t\t// after that message will expire\n\t\t\"x-message-ttl\": int32(b.getExpiresIn()),\n\t\t// Time after that the queue will be deleted.\n\t\t\"x-expires\": int32(b.getExpiresIn()),\n\t}\n\tconn, channel, _, _, _, err := b.Connect(\n\t\tb.GetConfig().ResultBackend,\n\t\t\"\",\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\ttaskUUID,                        // queue name\n\t\tfalse,                           // queue durable\n\t\ttrue,                            // queue delete when unused\n\t\ttaskUUID,                        // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tdeclareQueueArgs,                // queue declare args\n\t\tnil,                             // queue binding args\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer b.Close(channel, conn)\n\n\td, ok, err := channel.Get(\n\t\ttaskUUID, // queue name\n\t\tfalse,    // multiple\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !ok {\n\t\treturn nil, errors.New(\"No state ready\")\n\t}\n\n\td.Ack(false)\n\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader([]byte(d.Body)))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\tlog.ERROR.Printf(\"Failed to unmarshal task state: %s\", string(d.Body))\n\t\tlog.ERROR.Print(err)\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer b.Close(channel, conn)\n\n\treturn b.DeleteQueue(channel, taskUUID)\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tb.DeleteQueue(channel, amqmChordTriggeredQueue(groupUUID))\n\n\treturn b.DeleteQueue(channel, groupUUID)\n}\n\n// updateState saves current task state\nfunc (b *Backend) updateState(taskState *tasks.TaskState) error {\n\tmessage, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tdeclareQueueArgs := amqp.Table{\n\t\t// Time in milliseconds\n\t\t// after that message will expire\n\t\t\"x-message-ttl\": int32(b.getExpiresIn()),\n\t\t// Time after that the queue will be deleted.\n\t\t\"x-expires\": int32(b.getExpiresIn()),\n\t}\n\tconn, channel, queue, confirmsChan, _, err := b.Connect(\n\t\tb.GetConfig().ResultBackend,\n\t\t\"\",\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\ttaskState.TaskUUID,              // queue name\n\t\tfalse,                           // queue durable\n\t\ttrue,                            // queue delete when unused\n\t\ttaskState.TaskUUID,              // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tdeclareQueueArgs,                // queue declare args\n\t\tnil,                             // queue binding args\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tif err := channel.Publish(\n\t\tb.GetConfig().AMQP.Exchange, // exchange\n\t\tqueue.Name,                  // routing key\n\t\tfalse,                       // mandatory\n\t\tfalse,                       // immediate\n\t\tamqp.Publishing{\n\t\t\tContentType:  \"application/json\",\n\t\t\tBody:         message,\n\t\t\tDeliveryMode: amqp.Persistent, // Persistent // Transient\n\t\t},\n\t); err != nil {\n\t\treturn err\n\t}\n\n\tconfirmed := <-confirmsChan\n\n\tif confirmed.Ack {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"Failed delivery of delivery tag: %d\", confirmed.DeliveryTag)\n}\n\n// getExpiresIn returns expiration time\nfunc (b *Backend) getExpiresIn() int {\n\tresultsExpireIn := b.GetConfig().ResultsExpireIn * 1000\n\tif resultsExpireIn == 0 {\n\t\t// // expire results after 1 hour by default\n\t\tresultsExpireIn = config.DefaultResultsExpireIn * 1000\n\t}\n\treturn resultsExpireIn\n}\n\n// markTaskCompleted marks task as completed in either groupdUUID_success\n// or groupUUID_failure queue. This is important for GroupCompleted and\n// GroupSuccessful methods\nfunc (b *Backend) markTaskCompleted(signature *tasks.Signature, taskState *tasks.TaskState) error {\n\tif signature.GroupUUID == \"\" || signature.GroupTaskCount == 0 {\n\t\treturn nil\n\t}\n\n\tmessage, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tdeclareQueueArgs := amqp.Table{\n\t\t// Time in milliseconds\n\t\t// after that message will expire\n\t\t\"x-message-ttl\": int32(b.getExpiresIn()),\n\t\t// Time after that the queue will be deleted.\n\t\t\"x-expires\": int32(b.getExpiresIn()),\n\t}\n\tconn, channel, queue, confirmsChan, _, err := b.Connect(\n\t\tb.GetConfig().ResultBackend,\n\t\t\"\",\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\tsignature.GroupUUID,             // queue name\n\t\tfalse,                           // queue durable\n\t\ttrue,                            // queue delete when unused\n\t\tsignature.GroupUUID,             // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tdeclareQueueArgs,                // queue declare args\n\t\tnil,                             // queue binding args\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tif err := channel.Publish(\n\t\tb.GetConfig().AMQP.Exchange, // exchange\n\t\tqueue.Name,                  // routing key\n\t\tfalse,                       // mandatory\n\t\tfalse,                       // immediate\n\t\tamqp.Publishing{\n\t\t\tContentType:  \"application/json\",\n\t\t\tBody:         message,\n\t\t\tDeliveryMode: amqp.Persistent, // Persistent // Transient\n\t\t},\n\t); err != nil {\n\t\treturn err\n\t}\n\n\tconfirmed := <-confirmsChan\n\n\tif !confirmed.Ack {\n\t\treturn fmt.Errorf(\"Failed delivery of delivery tag: %v\", confirmed.DeliveryTag)\n\t}\n\n\treturn nil\n}\n\nfunc amqmChordTriggeredQueue(groupUUID string) string {\n\treturn fmt.Sprintf(\"%s_chord_triggered\", groupUUID)\n}\n"
  },
  {
    "path": "v1/backends/amqp/amqp_test.go",
    "content": "package amqp_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/amqp\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar (\n\tamqpConfig *config.Config\n)\n\nfunc init() {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tif amqpURL == \"\" {\n\t\treturn\n\t}\n\n\tfinalAmqpURL := amqpURL\n\tvar finalSeparator string\n\n\tamqpURLs := os.Getenv(\"AMQP_URLS\")\n\tif amqpURLs != \"\" {\n\t\tseparator := os.Getenv(\"AMQP_URLS_SEPARATOR\")\n\t\tif separator == \"\" {\n\t\t\treturn\n\t\t}\n\t\tfinalSeparator = separator\n\t\tfinalAmqpURL = amqpURLs\n\t}\n\n\tamqp2URL := os.Getenv(\"AMQP2_URL\")\n\tif amqp2URL == \"\" {\n\t\tamqp2URL = amqpURL\n\t}\n\n\tamqpConfig = &config.Config{\n\t\tBroker:                  finalAmqpURL,\n\t\tMultipleBrokerSeparator: finalSeparator,\n\t\tDefaultQueue:            \"test_queue\",\n\t\tResultBackend:           amqp2URL,\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t}\n}\n\nfunc TestGroupCompleted(t *testing.T) {\n\tif os.Getenv(\"AMQP_URL\") == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tgroupUUID := \"testGroupUUID\"\n\tgroupTaskCount := 2\n\ttask1 := &tasks.Signature{\n\t\tUUID:           \"testTaskUUID1\",\n\t\tGroupUUID:      groupUUID,\n\t\tGroupTaskCount: groupTaskCount,\n\t}\n\ttask2 := &tasks.Signature{\n\t\tUUID:           \"testTaskUUID2\",\n\t\tGroupUUID:      groupUUID,\n\t\tGroupTaskCount: groupTaskCount,\n\t}\n\n\tbackend := amqp.New(amqpConfig)\n\n\t// Cleanup before the test\n\tbackend.PurgeState(task1.UUID)\n\tbackend.PurgeState(task2.UUID)\n\tbackend.PurgeGroupMeta(groupUUID)\n\n\tgroupCompleted, err := backend.GroupCompleted(groupUUID, groupTaskCount)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.InitGroup(groupUUID, []string{task1.UUID, task2.UUID})\n\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, groupTaskCount)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.SetStatePending(task1)\n\tbackend.SetStateStarted(task2)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, groupTaskCount)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\ttaskResults := []*tasks.TaskResult{new(tasks.TaskResult)}\n\tbackend.SetStateSuccess(task1, taskResults)\n\tbackend.SetStateSuccess(task2, taskResults)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, groupTaskCount)\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, groupCompleted)\n\t}\n}\n\nfunc TestGetState(t *testing.T) {\n\tif os.Getenv(\"AMQP_URL\") == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tgo func() {\n\t\tbackend := amqp.New(amqpConfig)\n\t\tbackend.SetStatePending(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\tbackend.SetStateReceived(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\tbackend.SetStateStarted(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\n\t\ttaskResults := []*tasks.TaskResult{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: 2,\n\t\t\t},\n\t\t}\n\t\tbackend.SetStateSuccess(signature, taskResults)\n\t}()\n\n\tbackend := amqp.New(amqpConfig)\n\n\tvar (\n\t\ttaskState *tasks.TaskState\n\t\terr       error\n\t)\n\tfor {\n\t\ttaskState, err = backend.GetState(signature.UUID)\n\t\tif taskState == nil {\n\t\t\tassert.Equal(t, \"No state ready\", err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.NoError(t, err)\n\t\tif taskState.IsCompleted() {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc TestPurgeState(t *testing.T) {\n\tif os.Getenv(\"AMQP_URL\") == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := amqp.New(amqpConfig)\n\n\tbackend.SetStatePending(signature)\n\tbackend.SetStateReceived(signature)\n\ttaskState, err := backend.GetState(signature.UUID)\n\tassert.NotNil(t, taskState)\n\tassert.NoError(t, err)\n\n\tbackend.PurgeState(taskState.TaskUUID)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Nil(t, taskState)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "v1/backends/dynamodb/dynamodb.go",
    "content": "package dynamodb\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/service/dynamodb\"\n\t\"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute\"\n\t\"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface\"\n)\n\nconst (\n\tBatchItemsLimit  = 99\n\tMaxFetchAttempts = 3\n)\n\n// Backend ...\ntype Backend struct {\n\tcommon.Backend\n\tcnf    *config.Config\n\tclient dynamodbiface.DynamoDBAPI\n}\n\n// New creates a Backend instance\nfunc New(cnf *config.Config) iface.Backend {\n\tbackend := &Backend{Backend: common.NewBackend(cnf), cnf: cnf}\n\n\tif cnf.DynamoDB != nil && cnf.DynamoDB.Client != nil {\n\t\tbackend.client = cnf.DynamoDB.Client\n\t} else {\n\t\tsess := session.Must(session.NewSessionWithOptions(session.Options{\n\t\t\tSharedConfigState: session.SharedConfigEnable,\n\t\t}))\n\t\tbackend.client = dynamodb.New(sess)\n\t}\n\n\t// Check if needed tables exist\n\terr := backend.checkRequiredTablesIfExist()\n\tif err != nil {\n\t\tlog.FATAL.Printf(\"Failed to prepare tables. Error: %v\", err)\n\t}\n\treturn backend\n}\n\n// InitGroup ...\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tmeta := tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t\tTTL:       b.getExpirationTime(),\n\t}\n\tav, err := dynamodbattribute.MarshalMap(meta)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Error when marshaling Dynamodb attributes. Err: %v\", err)\n\t\treturn err\n\t}\n\tinput := &dynamodb.PutItemInput{\n\t\tItem:      av,\n\t\tTableName: aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t}\n\t_, err = b.client.PutItem(input)\n\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Got error when calling PutItem: %v; Error: %v\", input, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// GroupCompleted ...\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(groupMeta.TaskUUIDs)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates ...\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn b.getStates(groupMeta.TaskUUIDs)\n}\n\n// TriggerChord ...\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\t// Get the group meta data\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Chord has already been triggered, return false (should not trigger again)\n\tif groupMeta.ChordTriggered {\n\t\treturn false, nil\n\t}\n\n\t// If group meta is locked, wait until it's unlocked\n\tfor groupMeta.Lock {\n\t\tgroupMeta, _ = b.getGroupMeta(groupUUID)\n\t\tlog.WARNING.Printf(\"Group [%s] locked, waiting\", groupUUID)\n\t\ttime.Sleep(time.Millisecond * 5)\n\t}\n\n\t// Acquire lock\n\tif err = b.lockGroupMeta(groupUUID); err != nil {\n\t\treturn false, err\n\t}\n\tdefer b.unlockGroupMeta(groupUUID)\n\n\t// update group meta data\n\terr = b.chordTriggered(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn true, err\n}\n\n// SetStatePending ...\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\ttaskState := tasks.NewPendingTaskState(signature)\n\t// taskUUID is the primary key of the table, so a new task need to be created first, instead of using dynamodb.UpdateItemInput directly\n\treturn b.initTaskState(taskState)\n}\n\n// SetStateReceived ...\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\treturn b.setTaskState(taskState)\n}\n\n// SetStateStarted ...\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\ttaskState := tasks.NewStartedTaskState(signature)\n\treturn b.setTaskState(taskState)\n}\n\n// SetStateRetry ...\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\ttaskState := tasks.NewRetryTaskState(signature)\n\treturn b.setTaskState(taskState)\n}\n\n// SetStateSuccess ...\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\ttaskState.TTL = b.getExpirationTime()\n\treturn b.setTaskState(taskState)\n}\n\n// SetStateFailure ...\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\ttaskState.TTL = b.getExpirationTime()\n\treturn b.updateToFailureStateWithError(taskState)\n}\n\n// GetState ...\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\tresult, err := b.client.GetItem(&dynamodb.GetItemInput{\n\t\tTableName: aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t\tKey: map[string]*dynamodb.AttributeValue{\n\t\t\t\"TaskUUID\": {\n\t\t\t\tS: aws.String(taskUUID),\n\t\t\t},\n\t\t},\n\t\tConsistentRead: aws.Bool(true),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn b.unmarshalTaskStateGetItemResult(result)\n}\n\n// getStates returns the current states for the given list of tasks.\n// It uses batch fetch API. If any keys fail to fetch, it'll retry with exponential backoff until maxFetchAttempts times.\nfunc (b *Backend) getStates(tasksToFetch []string) ([]*tasks.TaskState, error) {\n\tfetchedTaskStates := make([]*tasks.TaskState, 0, len(tasksToFetch))\n\tvar unfetchedTaskIDs []string\n\n\t// try until all keys are fetched or until we run out of attempts.\n\tfor attempt := 0; len(tasksToFetch) > 0 && attempt < MaxFetchAttempts; attempt++ {\n\t\tunfetchedTaskIDs = nil\n\t\tfor _, batch := range chunkTasks(tasksToFetch, BatchItemsLimit) {\n\t\t\tfetched, unfetched, err := b.batchFetchTaskStates(batch)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfetchedTaskStates = append(fetchedTaskStates, fetched...)\n\t\t\tunfetchedTaskIDs = append(unfetchedTaskIDs, unfetched...)\n\t\t}\n\t\ttasksToFetch = unfetchedTaskIDs\n\n\t\t// Check if there were any tasks that were not fetched. If so, retry with exponential backoff.\n\t\tif len(unfetchedTaskIDs) > 0 {\n\t\t\tbackoffDuration := time.Duration(math.Pow(2, float64(attempt))) * time.Second\n\t\t\tlog.DEBUG.Printf(\"Unable to fetch [%d] keys on attempt [%d]. Sleeping for [%s]\", len(unfetchedTaskIDs), attempt+1, backoffDuration)\n\t\t\ttime.Sleep(backoffDuration)\n\t\t}\n\t}\n\n\tif len(unfetchedTaskIDs) > 0 {\n\t\treturn nil, fmt.Errorf(\"Failed to fetch [%d] keys even after retries: [%+v]\", len(unfetchedTaskIDs), unfetchedTaskIDs)\n\t}\n\n\treturn fetchedTaskStates, nil\n}\n\n// batchFetchTaskStates returns the current states of the given tasks by fetching them all in a single batched API.\n// DynamoDB's BatchGetItem() can return partial results. If there are any unfetched keys, they are returned as second\n// return value so that the caller can retry those keys.\n// https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/#DynamoDB.BatchGetItem\nfunc (b *Backend) batchFetchTaskStates(taskUUIDs []string) ([]*tasks.TaskState, []string, error) {\n\ttableName := b.cnf.DynamoDB.TaskStatesTable\n\tkeys := make([]map[string]*dynamodb.AttributeValue, len(taskUUIDs))\n\tfor i, tid := range taskUUIDs {\n\t\tkeys[i] = map[string]*dynamodb.AttributeValue{\n\t\t\t\"TaskUUID\": {\n\t\t\t\tS: aws.String(tid),\n\t\t\t},\n\t\t}\n\t}\n\n\tinput := &dynamodb.BatchGetItemInput{\n\t\tRequestItems: map[string]*dynamodb.KeysAndAttributes{\n\t\t\ttableName: {\n\t\t\t\tConsistentRead: aws.Bool(true),\n\t\t\t\tKeys:           keys,\n\t\t\t},\n\t\t},\n\t}\n\n\tresult, err := b.client.BatchGetItem(input)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"BatchGetItem failed. Error: [%s]\", err)\n\t}\n\n\tfetchedKeys, ok := result.Responses[tableName]\n\tif !ok {\n\t\treturn nil, nil, fmt.Errorf(\"no keys returned from the table: [%s]\", tableName)\n\t}\n\n\tstates := []*tasks.TaskState{}\n\tif err := dynamodbattribute.UnmarshalListOfMaps(fetchedKeys, &states); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"Got error when unmarshal map. Error: %v\", err)\n\t}\n\n\t// Look for any unprocessed keys\n\tvar unfetchedKeys []string\n\tif result.UnprocessedKeys[tableName] != nil {\n\t\tunfetchedKeys, err = getUnfetchedKeys(result.UnprocessedKeys[tableName])\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"unable to fetch some keys: [%+v]. Error: [%s]\", result.UnprocessedKeys, err)\n\t\t}\n\t}\n\n\treturn states, unfetchedKeys, nil\n}\n\n// PurgeState ...\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\tinput := &dynamodb.DeleteItemInput{\n\t\tKey: map[string]*dynamodb.AttributeValue{\n\t\t\t\"TaskUUID\": {\n\t\t\t\tN: aws.String(taskUUID),\n\t\t\t},\n\t\t},\n\t\tTableName: aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t}\n\t_, err := b.client.DeleteItem(input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// PurgeGroupMeta ...\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\tinput := &dynamodb.DeleteItemInput{\n\t\tKey: map[string]*dynamodb.AttributeValue{\n\t\t\t\"GroupUUID\": {\n\t\t\t\tN: aws.String(groupUUID),\n\t\t\t},\n\t\t},\n\t\tTableName: aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t}\n\t_, err := b.client.DeleteItem(input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) getGroupMeta(groupUUID string) (*tasks.GroupMeta, error) {\n\tresult, err := b.client.GetItem(&dynamodb.GetItemInput{\n\t\tTableName: aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t\tKey: map[string]*dynamodb.AttributeValue{\n\t\t\t\"GroupUUID\": {\n\t\t\t\tS: aws.String(groupUUID),\n\t\t\t},\n\t\t},\n\t\tConsistentRead: aws.Bool(true),\n\t})\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Error when getting group [%s]. Error: [%s]\", groupUUID, err)\n\t\treturn nil, err\n\t}\n\titem, err := b.unmarshalGroupMetaGetItemResult(result)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Failed to unmarshal item. Error: [%s], Result: [%+v]\", err, result)\n\t\treturn nil, err\n\t}\n\treturn item, nil\n}\n\nfunc (b *Backend) lockGroupMeta(groupUUID string) error {\n\terr := b.updateGroupMetaLock(groupUUID, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) unlockGroupMeta(groupUUID string) error {\n\terr := b.updateGroupMetaLock(groupUUID, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) updateGroupMetaLock(groupUUID string, status bool) error {\n\tinput := &dynamodb.UpdateItemInput{\n\t\tExpressionAttributeNames: map[string]*string{\n\t\t\t\"#L\": aws.String(\"Lock\"),\n\t\t},\n\t\tExpressionAttributeValues: map[string]*dynamodb.AttributeValue{\n\t\t\t\":l\": {\n\t\t\t\tBOOL: aws.Bool(status),\n\t\t\t},\n\t\t},\n\t\tKey: map[string]*dynamodb.AttributeValue{\n\t\t\t\"GroupUUID\": {\n\t\t\t\tS: aws.String(groupUUID),\n\t\t\t},\n\t\t},\n\t\tReturnValues:     aws.String(\"UPDATED_NEW\"),\n\t\tTableName:        aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t\tUpdateExpression: aws.String(\"SET #L = :l\"),\n\t}\n\n\t_, err := b.client.UpdateItem(input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) chordTriggered(groupUUID string) error {\n\tinput := &dynamodb.UpdateItemInput{\n\t\tExpressionAttributeNames: map[string]*string{\n\t\t\t\"#CT\": aws.String(\"ChordTriggered\"),\n\t\t},\n\t\tExpressionAttributeValues: map[string]*dynamodb.AttributeValue{\n\t\t\t\":ct\": {\n\t\t\t\tBOOL: aws.Bool(true),\n\t\t\t},\n\t\t},\n\t\tKey: map[string]*dynamodb.AttributeValue{\n\t\t\t\"GroupUUID\": {\n\t\t\t\tS: aws.String(groupUUID),\n\t\t\t},\n\t\t},\n\t\tReturnValues:     aws.String(\"UPDATED_NEW\"),\n\t\tTableName:        aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t\tUpdateExpression: aws.String(\"SET #CT = :ct\"),\n\t}\n\n\t_, err := b.client.UpdateItem(input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) setTaskState(taskState *tasks.TaskState) error {\n\texpAttributeNames := map[string]*string{\n\t\t\"#S\": aws.String(\"State\"),\n\t}\n\texpAttributeValues := map[string]*dynamodb.AttributeValue{\n\t\t\":s\": {\n\t\t\tS: aws.String(taskState.State),\n\t\t},\n\t}\n\tkeyAttributeValues := map[string]*dynamodb.AttributeValue{\n\t\t\"TaskUUID\": {\n\t\t\tS: aws.String(taskState.TaskUUID),\n\t\t},\n\t}\n\texp := \"SET #S = :s\"\n\tif !taskState.CreatedAt.IsZero() {\n\t\texpAttributeNames[\"#C\"] = aws.String(\"CreatedAt\")\n\t\texpAttributeValues[\":c\"] = &dynamodb.AttributeValue{\n\t\t\tS: aws.String(taskState.CreatedAt.String()),\n\t\t}\n\t\texp += \", #C = :c\"\n\t}\n\tif taskState.TTL > 0 {\n\t\texpAttributeNames[\"#T\"] = aws.String(\"TTL\")\n\t\texpAttributeValues[\":t\"] = &dynamodb.AttributeValue{\n\t\t\tN: aws.String(fmt.Sprintf(\"%d\", taskState.TTL)),\n\t\t}\n\t\texp += \", #T = :t\"\n\t}\n\tif taskState.Results != nil && len(taskState.Results) != 0 {\n\t\texpAttributeNames[\"#R\"] = aws.String(\"Results\")\n\t\tvar results []*dynamodb.AttributeValue\n\t\tfor _, r := range taskState.Results {\n\t\t\tavMap := map[string]*dynamodb.AttributeValue{\n\t\t\t\t\"Type\": {\n\t\t\t\t\tS: aws.String(r.Type),\n\t\t\t\t},\n\t\t\t\t\"Value\": {\n\t\t\t\t\tS: aws.String(fmt.Sprintf(\"%v\", r.Value)),\n\t\t\t\t},\n\t\t\t}\n\t\t\trs := &dynamodb.AttributeValue{\n\t\t\t\tM: avMap,\n\t\t\t}\n\t\t\tresults = append(results, rs)\n\t\t}\n\t\texpAttributeValues[\":r\"] = &dynamodb.AttributeValue{\n\t\t\tL: results,\n\t\t}\n\t\texp += \", #R = :r\"\n\t}\n\tinput := &dynamodb.UpdateItemInput{\n\t\tExpressionAttributeNames:  expAttributeNames,\n\t\tExpressionAttributeValues: expAttributeValues,\n\t\tKey:                       keyAttributeValues,\n\t\tReturnValues:              aws.String(\"UPDATED_NEW\"),\n\t\tTableName:                 aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t\tUpdateExpression:          aws.String(exp),\n\t}\n\n\t_, err := b.client.UpdateItem(input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) initTaskState(taskState *tasks.TaskState) error {\n\tav, err := dynamodbattribute.MarshalMap(taskState)\n\tinput := &dynamodb.PutItemInput{\n\t\tItem:      av,\n\t\tTableName: aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = b.client.PutItem(input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) updateToFailureStateWithError(taskState *tasks.TaskState) error {\n\tinput := &dynamodb.UpdateItemInput{\n\t\tExpressionAttributeNames: map[string]*string{\n\t\t\t\"#S\": aws.String(\"State\"),\n\t\t\t\"#E\": aws.String(\"Error\"),\n\t\t},\n\t\tExpressionAttributeValues: map[string]*dynamodb.AttributeValue{\n\t\t\t\":s\": {\n\t\t\t\tS: aws.String(taskState.State),\n\t\t\t},\n\t\t\t\":e\": {\n\t\t\t\tS: aws.String(taskState.Error),\n\t\t\t},\n\t\t},\n\t\tKey: map[string]*dynamodb.AttributeValue{\n\t\t\t\"TaskUUID\": {\n\t\t\t\tS: aws.String(taskState.TaskUUID),\n\t\t\t},\n\t\t},\n\t\tReturnValues:     aws.String(\"UPDATED_NEW\"),\n\t\tTableName:        aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t\tUpdateExpression: aws.String(\"SET #S = :s, #E = :e\"),\n\t}\n\n\tif taskState.TTL > 0 {\n\t\tinput.ExpressionAttributeNames[\"#T\"] = aws.String(\"TTL\")\n\t\tinput.ExpressionAttributeValues[\":t\"] = &dynamodb.AttributeValue{\n\t\t\tN: aws.String(fmt.Sprintf(\"%d\", taskState.TTL)),\n\t\t}\n\t\tinput.UpdateExpression = aws.String(aws.StringValue(input.UpdateExpression) + \", #T = :t\")\n\t}\n\n\t_, err := b.client.UpdateItem(input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) unmarshalGroupMetaGetItemResult(result *dynamodb.GetItemOutput) (*tasks.GroupMeta, error) {\n\tif result == nil {\n\t\terr := errors.New(\"task state is nil\")\n\t\tlog.ERROR.Printf(\"Got error when unmarshal map. Error: %v\", err)\n\t\treturn nil, err\n\t}\n\titem := tasks.GroupMeta{}\n\terr := dynamodbattribute.UnmarshalMap(result.Item, &item)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Got error when unmarshal map. Error: %v\", err)\n\t\treturn nil, err\n\t}\n\treturn &item, err\n}\n\nfunc (b *Backend) unmarshalTaskStateGetItemResult(result *dynamodb.GetItemOutput) (*tasks.TaskState, error) {\n\tif result == nil {\n\t\terr := errors.New(\"task state is nil\")\n\t\tlog.ERROR.Printf(\"Got error when unmarshal map. Error: %v\", err)\n\t\treturn nil, err\n\t}\n\tstate := tasks.TaskState{}\n\terr := dynamodbattribute.UnmarshalMap(result.Item, &state)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Got error when unmarshal map. Error: %v\", err)\n\t\treturn nil, err\n\t}\n\treturn &state, nil\n}\n\nfunc (b *Backend) checkRequiredTablesIfExist() error {\n\tvar (\n\t\ttaskTableName  = b.cnf.DynamoDB.TaskStatesTable\n\t\tgroupTableName = b.cnf.DynamoDB.GroupMetasTable\n\t\ttableNames     []*string\n\t\tstartFromTable *string\n\t)\n\tfor {\n\t\tresult, err := b.client.ListTables(&dynamodb.ListTablesInput{\n\t\t\tExclusiveStartTableName: startFromTable,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttableNames = append(tableNames, result.TableNames...)\n\t\tif result.LastEvaluatedTableName == nil {\n\t\t\tbreak\n\t\t}\n\t\tstartFromTable = result.LastEvaluatedTableName\n\t}\n\n\tif !b.tableExists(taskTableName, tableNames) {\n\t\treturn errors.New(\"task table doesn't exist\")\n\t}\n\tif !b.tableExists(groupTableName, tableNames) {\n\t\treturn errors.New(\"group table doesn't exist\")\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) tableExists(tableName string, tableNames []*string) bool {\n\tfor _, t := range tableNames {\n\t\tif tableName == *t {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (b *Backend) getExpirationTime() int64 {\n\texpiresIn := b.GetConfig().ResultsExpireIn\n\tif expiresIn == 0 {\n\t\t// expire results after 1 hour by default\n\t\texpiresIn = config.DefaultResultsExpireIn\n\t}\n\treturn time.Now().Add(time.Second * time.Duration(expiresIn)).Unix()\n}\n\n// getUnfetchedKeys returns keys that were not fetched in a batch request.\nfunc getUnfetchedKeys(unprocessed *dynamodb.KeysAndAttributes) ([]string, error) {\n\tstates := []*tasks.TaskState{}\n\tvar taskIDs []string\n\tif err := dynamodbattribute.UnmarshalListOfMaps(unprocessed.Keys, &states); err != nil {\n\t\treturn nil, fmt.Errorf(\"Got error when unmarshal map. Error: %v\", err)\n\t}\n\tfor _, s := range states {\n\t\ttaskIDs = append(taskIDs, s.TaskUUID)\n\t}\n\treturn taskIDs, nil\n}\n\n// chunkTasks chunks the list of strings into multiple smaller lists of specified size.\nfunc chunkTasks(array []string, chunkSize int) [][]string {\n\tvar result [][]string\n\tfor len(array) > 0 {\n\t\tsz := min(len(array), chunkSize)\n\t\tchunk := array[:sz]\n\t\tarray = array[sz:]\n\t\tresult = append(result, chunk)\n\t}\n\treturn result\n}\n\nfunc min(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "v1/backends/dynamodb/dynamodb_export_test.go",
    "content": "package dynamodb\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/service/dynamodb\"\n\t\"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface\"\n)\n\nvar (\n\tTestDynamoDBBackend    *Backend\n\tTestErrDynamoDBBackend *Backend\n\tTestCnf                *config.Config\n\tTestDBClient           dynamodbiface.DynamoDBAPI\n\tTestErrDBClient        dynamodbiface.DynamoDBAPI\n\tTestGroupMeta          *tasks.GroupMeta\n\tTestTask1              map[string]*dynamodb.AttributeValue\n\tTestTask2              map[string]*dynamodb.AttributeValue\n\tTestTask3              map[string]*dynamodb.AttributeValue\n)\n\ntype TestDynamoDBClient struct {\n\tdynamodbiface.DynamoDBAPI\n\tPutItemOverride      func(*dynamodb.PutItemInput) (*dynamodb.PutItemOutput, error)\n\tUpdateItemOverride   func(*dynamodb.UpdateItemInput) (*dynamodb.UpdateItemOutput, error)\n\tGetItemOverride      func(input *dynamodb.GetItemInput) (*dynamodb.GetItemOutput, error)\n\tBatchGetItemOverride func(*dynamodb.BatchGetItemInput) (*dynamodb.BatchGetItemOutput, error)\n}\n\nfunc (t *TestDynamoDBClient) ResetOverrides() {\n\tt.PutItemOverride = nil\n\tt.UpdateItemOverride = nil\n\tt.BatchGetItemOverride = nil\n}\n\nfunc (t *TestDynamoDBClient) PutItem(input *dynamodb.PutItemInput) (*dynamodb.PutItemOutput, error) {\n\tif t.PutItemOverride != nil {\n\t\treturn t.PutItemOverride(input)\n\t}\n\treturn &dynamodb.PutItemOutput{}, nil\n}\nfunc (t *TestDynamoDBClient) BatchGetItem(input *dynamodb.BatchGetItemInput) (*dynamodb.BatchGetItemOutput, error) {\n\tif t.BatchGetItemOverride != nil {\n\t\treturn t.BatchGetItemOverride(input)\n\t}\n\treturn &dynamodb.BatchGetItemOutput{}, nil\n}\n\nfunc (t *TestDynamoDBClient) GetItem(input *dynamodb.GetItemInput) (*dynamodb.GetItemOutput, error) {\n\tif t.GetItemOverride != nil {\n\t\treturn t.GetItemOverride(input)\n\t}\n\tvar output *dynamodb.GetItemOutput\n\tswitch *input.TableName {\n\tcase \"group_metas\":\n\t\toutput = &dynamodb.GetItemOutput{\n\t\t\tItem: map[string]*dynamodb.AttributeValue{\n\t\t\t\t\"TaskUUIDs\": {\n\t\t\t\t\tL: []*dynamodb.AttributeValue{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tS: aws.String(\"testTaskUUID1\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tS: aws.String(\"testTaskUUID2\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tS: aws.String(\"testTaskUUID3\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"ChordTriggered\": {\n\t\t\t\t\tBOOL: aws.Bool(false),\n\t\t\t\t},\n\t\t\t\t\"GroupUUID\": {\n\t\t\t\t\tS: aws.String(\"testGroupUUID\"),\n\t\t\t\t},\n\t\t\t\t\"Lock\": {\n\t\t\t\t\tBOOL: aws.Bool(false),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\tcase \"task_states\":\n\t\tif input.Key[\"TaskUUID\"] == nil {\n\t\t\toutput = &dynamodb.GetItemOutput{\n\t\t\t\tItem: map[string]*dynamodb.AttributeValue{\n\t\t\t\t\t\"Error\": {\n\t\t\t\t\t\tNULL: aws.Bool(false),\n\t\t\t\t\t},\n\t\t\t\t\t\"State\": {\n\t\t\t\t\t\tS: aws.String(tasks.StatePending),\n\t\t\t\t\t},\n\t\t\t\t\t\"TaskUUID\": {\n\t\t\t\t\t\tS: aws.String(\"testTaskUUID1\"),\n\t\t\t\t\t},\n\t\t\t\t\t\"Results:\": {\n\t\t\t\t\t\tNULL: aws.Bool(true),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\tif *(input.Key[\"TaskUUID\"].S) == \"testTaskUUID1\" {\n\t\t\t\toutput = &dynamodb.GetItemOutput{\n\t\t\t\t\tItem: TestTask1,\n\t\t\t\t}\n\t\t\t} else if *(input.Key[\"TaskUUID\"].S) == \"testTaskUUID2\" {\n\t\t\t\toutput = &dynamodb.GetItemOutput{\n\t\t\t\t\tItem: TestTask2,\n\t\t\t\t}\n\n\t\t\t} else if *(input.Key[\"TaskUUID\"].S) == \"testTaskUUID3\" {\n\t\t\t\toutput = &dynamodb.GetItemOutput{\n\t\t\t\t\tItem: TestTask3,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\treturn output, nil\n}\n\nfunc (t *TestDynamoDBClient) DeleteItem(*dynamodb.DeleteItemInput) (*dynamodb.DeleteItemOutput, error) {\n\treturn &dynamodb.DeleteItemOutput{}, nil\n}\n\nfunc (t *TestDynamoDBClient) UpdateItem(input *dynamodb.UpdateItemInput) (*dynamodb.UpdateItemOutput, error) {\n\tif t.UpdateItemOverride != nil {\n\t\treturn t.UpdateItemOverride(input)\n\t}\n\treturn &dynamodb.UpdateItemOutput{}, nil\n}\n\nfunc (t *TestDynamoDBClient) ListTables(*dynamodb.ListTablesInput) (*dynamodb.ListTablesOutput, error) {\n\treturn &dynamodb.ListTablesOutput{\n\t\tTableNames: []*string{\n\t\t\taws.String(\"group_metas\"),\n\t\t\taws.String(\"task_states\"),\n\t\t},\n\t}, nil\n}\n\n// Always returns error\ntype TestErrDynamoDBClient struct {\n\tdynamodbiface.DynamoDBAPI\n}\n\nfunc (t *TestErrDynamoDBClient) PutItem(*dynamodb.PutItemInput) (*dynamodb.PutItemOutput, error) {\n\treturn nil, errors.New(\"error when putting an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) GetItem(*dynamodb.GetItemInput) (*dynamodb.GetItemOutput, error) {\n\treturn nil, errors.New(\"error when getting an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) DeleteItem(*dynamodb.DeleteItemInput) (*dynamodb.DeleteItemOutput, error) {\n\treturn nil, errors.New(\"error when deleting an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) Scan(*dynamodb.ScanInput) (*dynamodb.ScanOutput, error) {\n\treturn nil, errors.New(\"error when scanning an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) UpdateItem(*dynamodb.UpdateItemInput) (*dynamodb.UpdateItemOutput, error) {\n\treturn nil, errors.New(\"error when updating an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) ListTables(*dynamodb.ListTablesInput) (*dynamodb.ListTablesOutput, error) {\n\treturn nil, errors.New(\"error when listing tables\")\n}\n\nfunc init() {\n\tTestCnf = &config.Config{\n\t\tResultBackend:   os.Getenv(\"DYNAMODB_URL\"),\n\t\tResultsExpireIn: 30,\n\t\tDynamoDB: &config.DynamoDBConfig{\n\t\t\tTaskStatesTable: \"task_states\",\n\t\t\tGroupMetasTable: \"group_metas\",\n\t\t},\n\t}\n\tTestDBClient = new(TestDynamoDBClient)\n\tTestDynamoDBBackend = &Backend{cnf: TestCnf, client: TestDBClient}\n\n\tTestErrDBClient = new(TestErrDynamoDBClient)\n\tTestErrDynamoDBBackend = &Backend{cnf: TestCnf, client: TestErrDBClient}\n\n\tTestGroupMeta = &tasks.GroupMeta{\n\t\tGroupUUID: \"testGroupUUID\",\n\t\tTaskUUIDs: []string{\"testTaskUUID1\", \"testTaskUUID2\", \"testTaskUUID3\"},\n\t}\n}\n\nfunc (b *Backend) GetConfig() *config.Config {\n\treturn b.cnf\n}\n\nfunc (b *Backend) GetClient() dynamodbiface.DynamoDBAPI {\n\treturn b.client\n}\n\nfunc (b *Backend) GetGroupMetaForTest(groupUUID string) (*tasks.GroupMeta, error) {\n\treturn b.getGroupMeta(groupUUID)\n}\n\nfunc (b *Backend) UnmarshalGroupMetaGetItemResultForTest(result *dynamodb.GetItemOutput) (*tasks.GroupMeta, error) {\n\treturn b.unmarshalGroupMetaGetItemResult(result)\n}\n\nfunc (b *Backend) UnmarshalTaskStateGetItemResultForTest(result *dynamodb.GetItemOutput) (*tasks.TaskState, error) {\n\treturn b.unmarshalTaskStateGetItemResult(result)\n}\n\nfunc (b *Backend) SetTaskStateForTest(taskState *tasks.TaskState) error {\n\treturn b.setTaskState(taskState)\n}\n\nfunc (b *Backend) ChordTriggeredForTest(groupUUID string) error {\n\treturn b.chordTriggered(groupUUID)\n}\n\nfunc (b *Backend) UpdateGroupMetaLockForTest(groupUUID string, status bool) error {\n\treturn b.updateGroupMetaLock(groupUUID, status)\n}\n\nfunc (b *Backend) UnlockGroupMetaForTest(groupUUID string) error {\n\treturn b.unlockGroupMeta(groupUUID)\n}\n\nfunc (b *Backend) LockGroupMetaForTest(groupUUID string) error {\n\treturn b.lockGroupMeta(groupUUID)\n}\n\nfunc (b *Backend) GetStatesForTest(taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\treturn b.getStates(taskUUIDs)\n}\n\nfunc (b *Backend) UpdateToFailureStateWithErrorForTest(taskState *tasks.TaskState) error {\n\treturn b.updateToFailureStateWithError(taskState)\n}\n\nfunc (b *Backend) TableExistsForTest(tableName string, tableNames []*string) bool {\n\treturn b.tableExists(tableName, tableNames)\n}\n\nfunc (b *Backend) CheckRequiredTablesIfExistForTest() error {\n\treturn b.checkRequiredTablesIfExist()\n}\n"
  },
  {
    "path": "v1/backends/dynamodb/dynamodb_test.go",
    "content": "package dynamodb_test\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/dynamodb\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/stretchr/testify/assert\"\n\n\tawsdynamodb \"github.com/aws/aws-sdk-go/service/dynamodb\"\n)\n\nfunc TestNew(t *testing.T) {\n\t// should call t.Skip if not connected to internet\n\tbackend := dynamodb.New(dynamodb.TestCnf)\n\tassert.IsType(t, new(dynamodb.Backend), backend)\n}\n\nfunc TestInitGroup(t *testing.T) {\n\tgroupUUID := \"testGroupUUID\"\n\ttaskUUIDs := []string{\"testTaskUUID1\", \"testTaskUUID2\", \"testTaskUUID3\"}\n\tlog.INFO.Println(dynamodb.TestDynamoDBBackend.GetConfig())\n\n\terr := dynamodb.TestDynamoDBBackend.InitGroup(groupUUID, taskUUIDs)\n\tassert.Nil(t, err)\n\n\terr = dynamodb.TestErrDynamoDBBackend.InitGroup(groupUUID, taskUUIDs)\n\tassert.NotNil(t, err)\n\n\t// assert proper TTL value is set in InitGroup()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 3 * 3600 // results should expire after 3 hours\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\t// Override DynamoDB PutItem() behavior\n\tvar isPutItemCalled bool\n\tclient.PutItemOverride = func(input *awsdynamodb.PutItemInput) (*awsdynamodb.PutItemOutput, error) {\n\t\tisPutItemCalled = true\n\t\tassert.NotNil(t, input)\n\n\t\tactualTTLStr := *input.Item[\"TTL\"].N\n\t\texpectedTTLTime := time.Now().Add(3 * time.Hour)\n\t\tassertTTLValue(t, expectedTTLTime, actualTTLStr)\n\n\t\treturn &awsdynamodb.PutItemOutput{}, nil\n\t}\n\terr = dynamodb.TestDynamoDBBackend.InitGroup(groupUUID, taskUUIDs)\n\tassert.Nil(t, err)\n\tassert.True(t, isPutItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc assertTTLValue(t *testing.T, expectedTTLTime time.Time, actualEncodedTTLValue string) {\n\tactualTTLTimestamp, err := strconv.ParseInt(actualEncodedTTLValue, 10, 64)\n\tassert.Nil(t, err)\n\tactualTTLTime := time.Unix(actualTTLTimestamp, 0)\n\tassert.WithinDuration(t, expectedTTLTime, actualTTLTime, time.Second)\n}\n\nfunc TestGroupCompleted(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\t// Override DynamoDB BatchGetItem() behavior\n\tvar isBatchGetItemCalled bool\n\tclient.BatchGetItemOverride = func(input *awsdynamodb.BatchGetItemInput) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\tisBatchGetItemCalled = true\n\t\tassert.NotNil(t, input)\n\t\tassert.Nil(t, input.Validate())\n\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]*awsdynamodb.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateSuccess)}},\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateSuccess)}},\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateFailure)}},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tgroupUUID := \"testGroupUUID\"\n\tisCompleted, err := dynamodb.TestDynamoDBBackend.GroupCompleted(groupUUID, 3)\n\tassert.Nil(t, err)\n\tassert.True(t, isCompleted)\n\tassert.True(t, isBatchGetItemCalled)\n\tclient.ResetOverrides()\n}\nfunc TestGroupCompletedReturnsError(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tclient.BatchGetItemOverride = func(input *awsdynamodb.BatchGetItemInput) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\treturn nil, fmt.Errorf(\"Simulating error from AWS\")\n\t}\n\tisCompleted, err := dynamodb.TestDynamoDBBackend.GroupCompleted(\"test\", 3)\n\tassert.NotNil(t, err)\n\tassert.False(t, isCompleted)\n\tclient.ResetOverrides()\n}\n\n// TestGroupCompletedReturnsFalse tests that the GroupCompleted() returns false when some tasks have not yet finished.\nfunc TestGroupCompletedReturnsFalse(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\t// Override DynamoDB BatchGetItem() behavior\n\tclient.BatchGetItemOverride = func(_ *awsdynamodb.BatchGetItemInput) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]*awsdynamodb.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateSuccess)}},\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateFailure)}},\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StatePending)}},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tisCompleted, err := dynamodb.TestDynamoDBBackend.GroupCompleted(\"testGroup\", 3)\n\tassert.Nil(t, err)\n\tassert.False(t, isCompleted)\n\tclient.ResetOverrides()\n}\n\n// TestGroupCompletedReturnsFalse tests that the GroupCompleted() retries the the request until MaxFetchAttempts before returning an error\nfunc TestGroupCompletedRetries(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\t// Override DynamoDB BatchGetItem() behavior\n\tvar countBatchGetItemAPICalls int\n\tclient.BatchGetItemOverride = func(_ *awsdynamodb.BatchGetItemInput) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\tcountBatchGetItemAPICalls++\n\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]*awsdynamodb.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateSuccess)}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tUnprocessedKeys: map[string]*awsdynamodb.KeysAndAttributes{\n\t\t\t\ttableName: {\n\t\t\t\t\tKeys: []map[string]*awsdynamodb.AttributeValue{\n\t\t\t\t\t\t{\"TaskUUID\": {S: aws.String(\"unfetchedTaskUUID1\")}},\n\t\t\t\t\t\t{\"TaskUUID\": {S: aws.String(\"unfetchedTaskUUID2\")}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\t_, err := dynamodb.TestDynamoDBBackend.GroupCompleted(\"testGroup\", 3)\n\tassert.NotNil(t, err)\n\tassert.Equal(t, dynamodb.MaxFetchAttempts, countBatchGetItemAPICalls)\n\tclient.ResetOverrides()\n}\n\n// TestGroupCompletedReturnsFalse tests that the GroupCompleted() retries the the request and returns success if all keys are fetched on retries.\nfunc TestGroupCompletedRetrieSuccess(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\t// Override DynamoDB BatchGetItem() behavior\n\tvar countBatchGetItemAPICalls int\n\tclient.BatchGetItemOverride = func(_ *awsdynamodb.BatchGetItemInput) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\tcountBatchGetItemAPICalls++\n\n\t\t// simulate unfetched keys on 1st attempt.\n\t\tif countBatchGetItemAPICalls == 1 {\n\t\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\t\tResponses: map[string][]map[string]*awsdynamodb.AttributeValue{\n\t\t\t\t\ttableName: {}, // no keys returned in this attempt.\n\t\t\t\t},\n\t\t\t\tUnprocessedKeys: map[string]*awsdynamodb.KeysAndAttributes{\n\t\t\t\t\ttableName: {\n\t\t\t\t\t\tKeys: []map[string]*awsdynamodb.AttributeValue{\n\t\t\t\t\t\t\t{\"TaskUUID\": {S: aws.String(\"unfetchedTaskUUID1\")}},\n\t\t\t\t\t\t\t{\"TaskUUID\": {S: aws.String(\"unfetchedTaskUUID2\")}},\n\t\t\t\t\t\t\t{\"TaskUUID\": {S: aws.String(\"unfetchedTaskUUID3\")}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, nil\n\n\t\t}\n\n\t\t// Return all keys in subsequent attempts.\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]*awsdynamodb.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateSuccess)}},\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateSuccess)}},\n\t\t\t\t\t{\"State\": {S: aws.String(tasks.StateSuccess)}},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tisCompleted, err := dynamodb.TestDynamoDBBackend.GroupCompleted(\"testGroup\", 3)\n\tassert.Nil(t, err)\n\tassert.True(t, isCompleted)\n\tassert.Equal(t, 2, countBatchGetItemAPICalls)\n\tclient.ResetOverrides()\n}\n\nfunc TestPrivateFuncGetGroupMeta(t *testing.T) {\n\tgroupUUID := \"testGroupUUID\"\n\tmeta, err := dynamodb.TestDynamoDBBackend.GetGroupMetaForTest(groupUUID)\n\titem := tasks.GroupMeta{\n\t\tGroupUUID:      \"testGroupUUID\",\n\t\tLock:           false,\n\t\tChordTriggered: false,\n\t\tTaskUUIDs: []string{\n\t\t\t\"testTaskUUID1\",\n\t\t\t\"testTaskUUID2\",\n\t\t\t\"testTaskUUID3\",\n\t\t},\n\t}\n\tassert.Nil(t, err)\n\tassert.EqualValues(t, item, *meta)\n\t_, err = dynamodb.TestErrDynamoDBBackend.GetGroupMetaForTest(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncUnmarshalTaskStateGetItemResult(t *testing.T) {\n\tresult := awsdynamodb.GetItemOutput{\n\t\tItem: map[string]*awsdynamodb.AttributeValue{\n\t\t\t\"Error\": {\n\t\t\t\tNULL: aws.Bool(true),\n\t\t\t},\n\t\t\t\"State\": {\n\t\t\t\tS: aws.String(tasks.StatePending),\n\t\t\t},\n\t\t\t\"TaskUUID\": {\n\t\t\t\tS: aws.String(\"testTaskUUID1\"),\n\t\t\t},\n\t\t\t\"Results:\": {\n\t\t\t\tNULL: aws.Bool(true),\n\t\t\t},\n\t\t},\n\t}\n\n\tinvalidResult := awsdynamodb.GetItemOutput{\n\t\tItem: map[string]*awsdynamodb.AttributeValue{\n\t\t\t\"Error\": {\n\t\t\t\tBOOL: aws.Bool(true),\n\t\t\t},\n\t\t\t\"State\": {\n\t\t\t\tS: aws.String(tasks.StatePending),\n\t\t\t},\n\t\t\t\"TaskUUID\": {\n\t\t\t\tS: aws.String(\"testTaskUUID1\"),\n\t\t\t},\n\t\t\t\"Results:\": {\n\t\t\t\tBOOL: aws.Bool(true),\n\t\t\t},\n\t\t},\n\t}\n\n\titem := tasks.TaskState{\n\t\tTaskUUID: \"testTaskUUID1\",\n\t\tResults:  nil,\n\t\tState:    tasks.StatePending,\n\t\tError:    \"\",\n\t}\n\tstate, err := dynamodb.TestErrDynamoDBBackend.UnmarshalTaskStateGetItemResultForTest(&result)\n\tassert.Nil(t, err)\n\tassert.EqualValues(t, item, *state)\n\n\t_, err = dynamodb.TestDynamoDBBackend.UnmarshalTaskStateGetItemResultForTest(nil)\n\tassert.NotNil(t, err)\n\n\t_, err = dynamodb.TestDynamoDBBackend.UnmarshalTaskStateGetItemResultForTest(&invalidResult)\n\tassert.NotNil(t, err)\n\n}\n\nfunc TestPrivateFuncUnmarshalGroupMetaGetItemResult(t *testing.T) {\n\tresult := awsdynamodb.GetItemOutput{\n\t\tItem: map[string]*awsdynamodb.AttributeValue{\n\t\t\t\"TaskUUIDs\": {\n\t\t\t\tL: []*awsdynamodb.AttributeValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tS: aws.String(\"testTaskUUID1\"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tS: aws.String(\"testTaskUUID2\"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tS: aws.String(\"testTaskUUID3\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"ChordTriggered\": {\n\t\t\t\tBOOL: aws.Bool(false),\n\t\t\t},\n\t\t\t\"GroupUUID\": {\n\t\t\t\tS: aws.String(\"testGroupUUID\"),\n\t\t\t},\n\t\t\t\"Lock\": {\n\t\t\t\tBOOL: aws.Bool(false),\n\t\t\t},\n\t\t},\n\t}\n\n\tinvalidResult := awsdynamodb.GetItemOutput{\n\t\tItem: map[string]*awsdynamodb.AttributeValue{\n\t\t\t\"TaskUUIDs\": {\n\t\t\t\tL: []*awsdynamodb.AttributeValue{\n\t\t\t\t\t{\n\t\t\t\t\t\tS: aws.String(\"testTaskUUID1\"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tS: aws.String(\"testTaskUUID2\"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tS: aws.String(\"testTaskUUID3\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"ChordTriggered\": {\n\t\t\t\tS: aws.String(\"false\"), // this attribute is invalid\n\t\t\t},\n\t\t\t\"GroupUUID\": {\n\t\t\t\tS: aws.String(\"testGroupUUID\"),\n\t\t\t},\n\t\t\t\"Lock\": {\n\t\t\t\tBOOL: aws.Bool(false),\n\t\t\t},\n\t\t},\n\t}\n\n\titem := tasks.GroupMeta{\n\t\tGroupUUID:      \"testGroupUUID\",\n\t\tLock:           false,\n\t\tChordTriggered: false,\n\t\tTaskUUIDs: []string{\n\t\t\t\"testTaskUUID1\",\n\t\t\t\"testTaskUUID2\",\n\t\t\t\"testTaskUUID3\",\n\t\t},\n\t}\n\tmeta, err := dynamodb.TestErrDynamoDBBackend.UnmarshalGroupMetaGetItemResultForTest(&result)\n\tassert.Nil(t, err)\n\tassert.EqualValues(t, item, *meta)\n\t_, err = dynamodb.TestErrDynamoDBBackend.UnmarshalGroupMetaGetItemResultForTest(nil)\n\tassert.NotNil(t, err)\n\n\t_, err = dynamodb.TestErrDynamoDBBackend.UnmarshalGroupMetaGetItemResultForTest(&invalidResult)\n\tassert.NotNil(t, err)\n\n}\n\nfunc TestPrivateFuncSetTaskState(t *testing.T) {\n\tsignature := &tasks.Signature{\n\t\tName: \"Test\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 1,\n\t\t\t},\n\t\t},\n\t}\n\tstate := tasks.NewPendingTaskState(signature)\n\terr := dynamodb.TestErrDynamoDBBackend.SetTaskStateForTest(state)\n\tassert.NotNil(t, err)\n\terr = dynamodb.TestDynamoDBBackend.SetTaskStateForTest(state)\n\tassert.Nil(t, err)\n}\n\n// verifyUpdateInput is a helper function to verify valid dynamoDB update input.\nfunc verifyUpdateInput(t *testing.T, input *awsdynamodb.UpdateItemInput, expectedTaskID string, expectedState string, expectedTTLTime time.Time) {\n\tassert.NotNil(t, input)\n\n\t// verify task ID\n\tassert.Equal(t, expectedTaskID, *input.Key[\"TaskUUID\"].S)\n\n\t// verify task state\n\tassert.Equal(t, expectedState, *input.ExpressionAttributeValues[\":s\"].S)\n\n\t// Verify TTL\n\tif !expectedTTLTime.IsZero() {\n\t\tactualTTLStr := *input.ExpressionAttributeValues[\":t\"].N\n\t\tassertTTLValue(t, expectedTTLTime, actualTTLStr)\n\t}\n}\n\nfunc TestSetStateSuccess(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and TTL value is set in SetStateSuccess()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 3 * 3600 // results should expire after 3 hours\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\t// Override DynamoDB UpdateItem() behavior\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(input *awsdynamodb.UpdateItemInput) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateSuccess, time.Now().Add(3*time.Hour))\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateSuccess(signature, nil)\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestSetStateFailure(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and TTL value is set in SetStateFailure()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 2 * 3600 // results should expire after 2 hours\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\t// Override DynamoDB UpdateItem() behavior\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(input *awsdynamodb.UpdateItemInput) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateFailure, time.Now().Add(2*time.Hour))\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateFailure(signature, \"Some error occurred\")\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestSetStateReceived(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and *no* TTL value is set in SetStateReceived()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 2 * 3600 // results should expire after 2 hours (ignored for this state)\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(input *awsdynamodb.UpdateItemInput) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateReceived, time.Time{})\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateReceived(signature)\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestSetStateStarted(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and *no* TTL value is set in SetStateStarted()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 2 * 3600 // results should expire after 2 hours (ignored for this state)\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(input *awsdynamodb.UpdateItemInput) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateStarted, time.Time{})\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateStarted(signature)\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestSetStateRetry(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and *no* TTL value is set in SetStateStarted()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 2 * 3600 // results should expire after 2 hours (ignored for this state)\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(input *awsdynamodb.UpdateItemInput) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateRetry, time.Time{})\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateRetry(signature)\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestGroupTaskStates(t *testing.T) {\n\texpectedStates := map[string]*tasks.TaskState{\n\t\t\"testTaskUUID1\": {\n\t\t\tTaskUUID: \"testTaskUUID1\",\n\t\t\tResults:  nil,\n\t\t\tState:    tasks.StatePending,\n\t\t\tError:    \"\",\n\t\t},\n\t\t\"testTaskUUID2\": {\n\t\t\tTaskUUID: \"testTaskUUID2\",\n\t\t\tResults:  nil,\n\t\t\tState:    tasks.StateStarted,\n\t\t\tError:    \"\",\n\t\t},\n\t\t\"testTaskUUID3\": {\n\t\t\tTaskUUID: \"testTaskUUID3\",\n\t\t\tResults:  nil,\n\t\t\tState:    tasks.StateSuccess,\n\t\t\tError:    \"\",\n\t\t},\n\t}\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\tclient.BatchGetItemOverride = func(input *awsdynamodb.BatchGetItemInput) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\tassert.Nil(t, input.Validate())\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]*awsdynamodb.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\n\t\t\t\t\t\t\"TaskUUID\": {S: aws.String(\"testTaskUUID1\")},\n\t\t\t\t\t\t\"Results:\": {NULL: aws.Bool(true)},\n\t\t\t\t\t\t\"State\":    {S: aws.String(tasks.StatePending)},\n\t\t\t\t\t\t\"Error\":    {NULL: aws.Bool(true)},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"TaskUUID\": {S: aws.String(\"testTaskUUID2\")},\n\t\t\t\t\t\t\"Results:\": {NULL: aws.Bool(true)},\n\t\t\t\t\t\t\"State\":    {S: aws.String(tasks.StateStarted)},\n\t\t\t\t\t\t\"Error\":    {NULL: aws.Bool(true)},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"TaskUUID\": {S: aws.String(\"testTaskUUID3\")},\n\t\t\t\t\t\t\"Results:\": {NULL: aws.Bool(true)},\n\t\t\t\t\t\t\"State\":    {S: aws.String(tasks.StateSuccess)},\n\t\t\t\t\t\t\"Error\":    {NULL: aws.Bool(true)},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tdefer client.ResetOverrides()\n\n\tstates, err := dynamodb.TestDynamoDBBackend.GroupTaskStates(\"testGroupUUID\", 3)\n\tassert.Nil(t, err)\n\tfor _, s := range states {\n\t\tassert.EqualValues(t, *s, *expectedStates[s.TaskUUID])\n\t}\n}\n\nfunc TestTriggerChord(t *testing.T) {\n\tgroupUUID := \"testGroupUUID\"\n\ttriggered, err := dynamodb.TestDynamoDBBackend.TriggerChord(groupUUID)\n\tassert.Nil(t, err)\n\tassert.True(t, triggered)\n}\n\nfunc TestGetState(t *testing.T) {\n\ttaskUUID := \"testTaskUUID1\"\n\texpectedState := &tasks.TaskState{\n\t\tTaskUUID: \"testTaskUUID1\",\n\t\tResults:  nil,\n\t\tState:    tasks.StatePending,\n\t\tError:    \"\",\n\t}\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tclient.GetItemOverride = func(input *awsdynamodb.GetItemInput) (*awsdynamodb.GetItemOutput, error) {\n\t\treturn &awsdynamodb.GetItemOutput{\n\t\t\tItem: map[string]*awsdynamodb.AttributeValue{\n\t\t\t\t\"TaskUUID\": {S: aws.String(\"testTaskUUID1\")},\n\t\t\t\t\"Results:\": {NULL: aws.Bool(true)},\n\t\t\t\t\"State\":    {S: aws.String(tasks.StatePending)},\n\t\t\t\t\"Error\":    {NULL: aws.Bool(false)},\n\t\t\t},\n\t\t}, nil\n\t}\n\tdefer client.ResetOverrides()\n\n\tstate, err := dynamodb.TestDynamoDBBackend.GetState(taskUUID)\n\tassert.Nil(t, err)\n\tassert.EqualValues(t, expectedState, state)\n}\n\nfunc TestPurgeState(t *testing.T) {\n\ttaskUUID := \"testTaskUUID1\"\n\terr := dynamodb.TestDynamoDBBackend.PurgeState(taskUUID)\n\tassert.Nil(t, err)\n\n\terr = dynamodb.TestErrDynamoDBBackend.PurgeState(taskUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPurgeGroupMeta(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.PurgeGroupMeta(groupUUID)\n\tassert.Nil(t, err)\n\n\terr = dynamodb.TestErrDynamoDBBackend.PurgeGroupMeta(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncLockGroupMeta(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.LockGroupMetaForTest(groupUUID)\n\tassert.Nil(t, err)\n\terr = dynamodb.TestErrDynamoDBBackend.LockGroupMetaForTest(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncUnLockGroupMeta(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.UnlockGroupMetaForTest(groupUUID)\n\tassert.Nil(t, err)\n\terr = dynamodb.TestErrDynamoDBBackend.UnlockGroupMetaForTest(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncChordTriggered(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.ChordTriggeredForTest(groupUUID)\n\tassert.Nil(t, err)\n\terr = dynamodb.TestErrDynamoDBBackend.ChordTriggeredForTest(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestDynamoDBPrivateFuncUpdateGroupMetaLock(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.UpdateGroupMetaLockForTest(groupUUID, true)\n\tassert.Nil(t, err)\n\terr = dynamodb.TestErrDynamoDBBackend.UpdateGroupMetaLockForTest(groupUUID, true)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncUpdateToFailureStateWithError(t *testing.T) {\n\tsignature := &tasks.Signature{\n\t\tName: \"Test\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 1,\n\t\t\t},\n\t\t},\n\t}\n\n\tstate := tasks.NewFailureTaskState(signature, \"This is an error\")\n\terr := dynamodb.TestDynamoDBBackend.UpdateToFailureStateWithErrorForTest(state)\n\tassert.Nil(t, err)\n}\n\nfunc TestPrivateFuncTableExistsForTest(t *testing.T) {\n\ttables := []*string{aws.String(\"foo\")}\n\tassert.False(t, dynamodb.TestDynamoDBBackend.TableExistsForTest(\"bar\", tables))\n\tassert.True(t, dynamodb.TestDynamoDBBackend.TableExistsForTest(\"foo\", tables))\n}\n\nfunc TestPrivateFuncCheckRequiredTablesIfExistForTest(t *testing.T) {\n\terr := dynamodb.TestDynamoDBBackend.CheckRequiredTablesIfExistForTest()\n\tassert.Nil(t, err)\n\ttaskTable := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\tgroupTable := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.GroupMetasTable\n\terr = dynamodb.TestErrDynamoDBBackend.CheckRequiredTablesIfExistForTest()\n\tassert.NotNil(t, err)\n\tdynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable = \"foo\"\n\terr = dynamodb.TestDynamoDBBackend.CheckRequiredTablesIfExistForTest()\n\tassert.NotNil(t, err)\n\tdynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable = taskTable\n\tdynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.GroupMetasTable = \"foo\"\n\terr = dynamodb.TestDynamoDBBackend.CheckRequiredTablesIfExistForTest()\n\tassert.NotNil(t, err)\n\tdynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.GroupMetasTable = groupTable\n}\n"
  },
  {
    "path": "v1/backends/eager/eager.go",
    "content": "package eager\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// ErrGroupNotFound ...\ntype ErrGroupNotFound struct {\n\tgroupUUID string\n}\n\n// NewErrGroupNotFound returns new instance of ErrGroupNotFound\nfunc NewErrGroupNotFound(groupUUID string) ErrGroupNotFound {\n\treturn ErrGroupNotFound{groupUUID: groupUUID}\n}\n\n// Error implements error interface\nfunc (e ErrGroupNotFound) Error() string {\n\treturn fmt.Sprintf(\"Group not found: %v\", e.groupUUID)\n}\n\n// ErrTasknotFound ...\ntype ErrTasknotFound struct {\n\ttaskUUID string\n}\n\n// NewErrTasknotFound returns new instance of ErrTasknotFound\nfunc NewErrTasknotFound(taskUUID string) ErrTasknotFound {\n\treturn ErrTasknotFound{taskUUID: taskUUID}\n}\n\n// Error implements error interface\nfunc (e ErrTasknotFound) Error() string {\n\treturn fmt.Sprintf(\"Task not found: %v\", e.taskUUID)\n}\n\n// Backend represents an \"eager\" in-memory result backend\ntype Backend struct {\n\tcommon.Backend\n\tgroups     map[string][]string\n\ttasks      map[string][]byte\n\tstateMutex sync.Mutex\n}\n\n// New creates EagerBackend instance\nfunc New() iface.Backend {\n\treturn &Backend{\n\t\tBackend: common.NewBackend(new(config.Config)),\n\t\tgroups:  make(map[string][]string),\n\t\ttasks:   make(map[string][]byte),\n\t}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\ttasks := make([]string, 0, len(taskUUIDs))\n\t// copy every task\n\ttasks = append(tasks, taskUUIDs...)\n\n\tb.groups[groupUUID] = tasks\n\treturn nil\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\ttasks, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn false, NewErrGroupNotFound(groupUUID)\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, v := range tasks {\n\t\tt, err := b.GetState(v)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif t.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\ttaskUUIDs, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn nil, NewErrGroupNotFound(groupUUID)\n\t}\n\n\tret := make([]*tasks.TaskState, 0, groupTaskCount)\n\tfor _, taskUUID := range taskUUIDs {\n\t\tt, err := b.GetState(taskUUID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tret = append(ret, t)\n\t}\n\n\treturn ret, nil\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\treturn true, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\tstate := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\tstate := tasks.NewReceivedTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\tstate := tasks.NewStartedTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tstate := tasks.NewRetryTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\tstate := tasks.NewSuccessTaskState(signature, results)\n\treturn b.updateState(state)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\tstate := tasks.NewFailureTaskState(signature, err)\n\treturn b.updateState(state)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\ttasktStateBytes, ok := b.tasks[taskUUID]\n\tif !ok {\n\t\treturn nil, NewErrTasknotFound(taskUUID)\n\t}\n\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader(tasktStateBytes))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to unmarshal task state %v\", b)\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\t_, ok := b.tasks[taskUUID]\n\tif !ok {\n\t\treturn NewErrTasknotFound(taskUUID)\n\t}\n\n\tdelete(b.tasks, taskUUID)\n\treturn nil\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\t_, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn NewErrGroupNotFound(groupUUID)\n\t}\n\n\tdelete(b.groups, groupUUID)\n\treturn nil\n}\n\nfunc (b *Backend) updateState(s *tasks.TaskState) error {\n\t// simulate the behavior of json marshal/unmarshal\n\tb.stateMutex.Lock()\n\tdefer b.stateMutex.Unlock()\n\tmsg, err := json.Marshal(s)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Marshal task state error: %v\", err)\n\t}\n\n\tb.tasks[s.TaskUUID] = msg\n\treturn nil\n}\n"
  },
  {
    "path": "v1/backends/eager/eager_test.go",
    "content": "package eager_test\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/eager\"\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype EagerBackendTestSuite struct {\n\tsuite.Suite\n\n\tbackend iface.Backend\n\tst      []*tasks.Signature\n\tgroups  []struct {\n\t\tid    string\n\t\ttasks []string\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) SetupSuite() {\n\t// prepare common test data\n\ts.backend = eager.New()\n\n\t// 2 non-group state\n\ts.st = []*tasks.Signature{\n\t\t{UUID: \"1\"},\n\t\t{UUID: \"2\"},\n\t\t{UUID: \"3\"},\n\t\t{UUID: \"4\"},\n\t\t{UUID: \"5\"},\n\t\t{UUID: \"6\"},\n\t}\n\n\tfor _, t := range s.st {\n\t\ts.backend.SetStatePending(t)\n\t}\n\n\t// groups\n\ts.groups = []struct {\n\t\tid    string\n\t\ttasks []string\n\t}{\n\t\t{\"group1\", []string{\"1-3\", \"1-4\"}},\n\t\t{\"group2\", []string{\"2-1\", \"2-2\", \"2-3\"}},\n\t\t{\"group3\", []string(nil)},\n\t\t{\"group4\", []string{\"4-1\", \"4-2\", \"4-3\", \"4-4\"}},\n\t\t{\"group5\", []string{\"5-1\", \"5-2\"}},\n\t}\n\n\tfor _, g := range s.groups {\n\t\tfor _, t := range g.tasks {\n\t\t\tsig := &tasks.Signature{\n\t\t\t\tUUID:           t,\n\t\t\t\tGroupUUID:      g.id,\n\t\t\t\tGroupTaskCount: len(g.tasks),\n\t\t\t}\n\t\t\ts.st = append(s.st, sig)\n\n\t\t\t// default state is pending\n\t\t\ts.backend.SetStatePending(sig)\n\t\t}\n\n\t\ts.Nil(s.backend.InitGroup(g.id, g.tasks))\n\t}\n\n\t// prepare for TestInitGroup\n\ts.Nil(s.backend.PurgeGroupMeta(s.groups[4].id))\n}\n\n//\n// Test Cases\n//\n\nfunc (s *EagerBackendTestSuite) TestInitGroup() {\n\t// group 5\n\t{\n\t\tg := s.groups[4]\n\t\ts.Nil(s.backend.InitGroup(g.id, g.tasks))\n\t}\n\n\t// group3 -- nil as task list\n\t{\n\t\tg := s.groups[2]\n\t\ts.Nil(s.backend.InitGroup(g.id, g.tasks))\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestGroupCompleted() {\n\t// group 1\n\t{\n\t\t// all tasks are pending\n\t\tg := s.groups[0]\n\t\tcompleted, err := s.backend.GroupCompleted(g.id, len(g.tasks))\n\t\ts.False(completed)\n\t\ts.Nil(err)\n\n\t\t// make these tasks success\n\t\tfor _, id := range g.tasks {\n\t\t\tt := s.getTaskSignature(id)\n\t\t\ts.NotNil(t)\n\t\t\tif t == nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ts.backend.SetStateSuccess(t, nil)\n\t\t}\n\n\t\tcompleted, err = s.backend.GroupCompleted(g.id, len(g.tasks))\n\t\ts.True(completed)\n\t\ts.Nil(err)\n\t}\n\n\t// group 2\n\t{\n\t\tg := s.groups[1]\n\n\t\tcompleted, err := s.backend.GroupCompleted(g.id, len(g.tasks))\n\t\ts.False(completed)\n\t\ts.Nil(err)\n\n\t\t// make these tasks failure\n\t\tfor _, id := range g.tasks {\n\t\t\tt := s.getTaskSignature(id)\n\t\t\ts.NotNil(t)\n\t\t\tif t == nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ts.backend.SetStateFailure(t, \"just a test\")\n\t\t}\n\n\t\tcompleted, err = s.backend.GroupCompleted(g.id, len(g.tasks))\n\t\ts.True(completed)\n\t\ts.Nil(err)\n\t}\n\n\t{\n\t\t// call on a not-existed group\n\t\tcompleted, err := s.backend.GroupCompleted(\"\", 0)\n\t\ts.False(completed)\n\t\ts.NotNil(err)\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestGroupTaskStates() {\n\t// group 4\n\t{\n\t\tg := s.groups[3]\n\n\t\t// set failure state with taskUUID as error message\n\t\tfor _, id := range g.tasks {\n\t\t\tt := s.getTaskSignature(id)\n\t\t\ts.NotNil(t)\n\t\t\tif t == nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ts.backend.SetStateFailure(t, t.UUID)\n\t\t}\n\n\t\t// get states back\n\t\tts, err := s.backend.GroupTaskStates(g.id, len(g.tasks))\n\t\ts.NotNil(ts)\n\t\ts.Nil(err)\n\t\tfor _, t := range ts {\n\t\t\ts.Equal(t.TaskUUID, t.Error)\n\t\t}\n\t}\n\n\t{\n\t\t// call on a not-existed group\n\t\tts, err := s.backend.GroupTaskStates(\"\", 0)\n\t\ts.Nil(ts)\n\t\ts.NotNil(err)\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStatePending() {\n\t// task 1\n\t{\n\t\tt := s.st[0]\n\n\t\t// change this state to receiving\n\t\ts.backend.SetStateReceived(t)\n\n\t\t// change it back to pending\n\t\ts.backend.SetStatePending(t)\n\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StatePending, st.State)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateReceived() {\n\t// task2\n\t{\n\t\tt := s.st[1]\n\t\ts.backend.SetStateReceived(t)\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StateReceived, st.State)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateStarted() {\n\t// task3\n\t{\n\t\tt := s.st[2]\n\t\ts.backend.SetStateStarted(t)\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StateStarted, st.State)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateSuccess() {\n\t// task4\n\t{\n\t\tt := s.st[3]\n\t\ttaskResults := []*tasks.TaskResult{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: json.Number(\"300.0\"),\n\t\t\t},\n\t\t}\n\t\ts.backend.SetStateSuccess(t, taskResults)\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\ts.NotNil(st)\n\n\t\ts.Equal(tasks.StateSuccess, st.State)\n\t\ts.Equal(taskResults, st.Results)\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateFailure() {\n\t// task5\n\t{\n\t\tt := s.st[4]\n\t\ts.backend.SetStateFailure(t, \"error\")\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StateFailure, st.State)\n\t\t\ts.Equal(\"error\", st.Error)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateRetry() {\n\t// task6\n\t{\n\t\tt := s.st[5]\n\t\ts.backend.SetStateRetry(t)\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StateRetry, st.State)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestGetState() {\n\t// get something not existed -- empty string\n\tst, err := s.backend.GetState(\"\")\n\ts.Nil(st)\n\ts.NotNil(err)\n}\n\nfunc (s *EagerBackendTestSuite) TestPurgeState() {\n\t// task6\n\t{\n\t\tt := s.st[5]\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.NotNil(st)\n\t\ts.Nil(err)\n\n\t\t// purge it\n\t\ts.Nil(s.backend.PurgeState(t.UUID))\n\n\t\t// should be not found\n\t\tst, err = s.backend.GetState(t.UUID)\n\t\ts.Nil(st)\n\t\ts.NotNil(err)\n\t}\n\n\t{\n\t\t// purge a not-existed state\n\t\ts.NotNil(s.backend.PurgeState(\"\"))\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestPurgeGroupMeta() {\n\t// group4\n\t{\n\t\tg := s.groups[3]\n\t\tts, err := s.backend.GroupTaskStates(g.id, len(g.tasks))\n\t\ts.NotNil(ts)\n\t\ts.Nil(err)\n\n\t\t// purge group\n\t\ts.Nil(s.backend.PurgeGroupMeta(g.id))\n\n\t\t// should be not found\n\t\tts, err = s.backend.GroupTaskStates(g.id, len(g.tasks))\n\t\ts.Nil(ts)\n\t\ts.NotNil(err)\n\t}\n\n\t{\n\t\t// purge a not-existed group\n\t\ts.NotNil(s.backend.PurgeGroupMeta(\"\"))\n\t}\n}\n\n//\n// internal method\n//\nfunc (s *EagerBackendTestSuite) getTaskSignature(taskUUID string) *tasks.Signature {\n\tfor _, v := range s.st {\n\t\tif v.UUID == taskUUID {\n\t\t\treturn v\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc TestEagerBackendMain(t *testing.T) {\n\tsuite.Run(t, &EagerBackendTestSuite{})\n}\n"
  },
  {
    "path": "v1/backends/iface/interfaces.go",
    "content": "package iface\n\nimport (\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// Backend - a common interface for all result backends\ntype Backend interface {\n\t// Group related functions\n\tInitGroup(groupUUID string, taskUUIDs []string) error\n\tGroupCompleted(groupUUID string, groupTaskCount int) (bool, error)\n\tGroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error)\n\tTriggerChord(groupUUID string) (bool, error)\n\n\t// Setting / getting task state\n\tSetStatePending(signature *tasks.Signature) error\n\tSetStateReceived(signature *tasks.Signature) error\n\tSetStateStarted(signature *tasks.Signature) error\n\tSetStateRetry(signature *tasks.Signature) error\n\tSetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error\n\tSetStateFailure(signature *tasks.Signature, err string) error\n\tGetState(taskUUID string) (*tasks.TaskState, error)\n\n\t// Purging stored stored tasks states and group meta data\n\tIsAMQP() bool\n\tPurgeState(taskUUID string) error\n\tPurgeGroupMeta(groupUUID string) error\n}\n"
  },
  {
    "path": "v1/backends/memcache/memcache.go",
    "content": "package memcache\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\n\tgomemcache \"github.com/bradfitz/gomemcache/memcache\"\n)\n\n// Backend represents a Memcache result backend\ntype Backend struct {\n\tcommon.Backend\n\tservers []string\n\tclient  *gomemcache.Client\n}\n\n// New creates Backend instance\nfunc New(cnf *config.Config, servers []string) iface.Backend {\n\treturn &Backend{\n\t\tBackend: common.NewBackend(cnf),\n\t\tservers: servers,\n\t}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tgroupMeta := &tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n\n\tencoded, err := json.Marshal(&groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.getClient().Set(&gomemcache.Item{\n\t\tKey:        groupUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t})\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(groupMeta.TaskUUIDs...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn []*tasks.TaskState{}, err\n\t}\n\n\treturn b.getStates(groupMeta.TaskUUIDs...)\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Chord has already been triggered, return false (should not trigger again)\n\tif groupMeta.ChordTriggered {\n\t\treturn false, nil\n\t}\n\n\t// If group meta is locked, wait until it's unlocked\n\tfor groupMeta.Lock {\n\t\tgroupMeta, _ = b.getGroupMeta(groupUUID)\n\t\tlog.WARNING.Print(\"Group meta locked, waiting\")\n\t\ttime.Sleep(time.Millisecond * 5)\n\t}\n\n\t// Acquire lock\n\tif err = b.lockGroupMeta(groupMeta); err != nil {\n\t\treturn false, err\n\t}\n\tdefer b.unlockGroupMeta(groupMeta)\n\n\t// Update the group meta data\n\tgroupMeta.ChordTriggered = true\n\tencoded, err := json.Marshal(&groupMeta)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif err = b.getClient().Replace(&gomemcache.Item{\n\t\tKey:        groupUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t}); err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\ttaskState := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\ttaskState := tasks.NewStartedTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tstate := tasks.NewRetryTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\treturn b.updateState(taskState)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\treturn b.updateState(taskState)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\titem, err := b.getClient().Get(taskUUID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader(item.Value))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\treturn b.getClient().Delete(taskUUID)\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\treturn b.getClient().Delete(groupUUID)\n}\n\n// updateState saves current task state\nfunc (b *Backend) updateState(taskState *tasks.TaskState) error {\n\tencoded, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.getClient().Set(&gomemcache.Item{\n\t\tKey:        taskState.TaskUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t})\n}\n\n// lockGroupMeta acquires lock on group meta data\nfunc (b *Backend) lockGroupMeta(groupMeta *tasks.GroupMeta) error {\n\tgroupMeta.Lock = true\n\tencoded, err := json.Marshal(groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.getClient().Set(&gomemcache.Item{\n\t\tKey:        groupMeta.GroupUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t})\n}\n\n// unlockGroupMeta releases lock on group meta data\nfunc (b *Backend) unlockGroupMeta(groupMeta *tasks.GroupMeta) error {\n\tgroupMeta.Lock = false\n\tencoded, err := json.Marshal(groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.getClient().Set(&gomemcache.Item{\n\t\tKey:        groupMeta.GroupUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t})\n}\n\n// getGroupMeta retrieves group meta data, convenience function to avoid repetition\nfunc (b *Backend) getGroupMeta(groupUUID string) (*tasks.GroupMeta, error) {\n\titem, err := b.getClient().Get(groupUUID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroupMeta := new(tasks.GroupMeta)\n\tdecoder := json.NewDecoder(bytes.NewReader(item.Value))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(groupMeta); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn groupMeta, nil\n}\n\n// getStates returns multiple task states\nfunc (b *Backend) getStates(taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\tstates := make([]*tasks.TaskState, len(taskUUIDs))\n\n\tfor i, taskUUID := range taskUUIDs {\n\t\titem, err := b.getClient().Get(taskUUID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tstate := new(tasks.TaskState)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(item.Value))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(state); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tstates[i] = state\n\t}\n\n\treturn states, nil\n}\n\n// getExpirationTimestamp returns expiration timestamp\nfunc (b *Backend) getExpirationTimestamp() int32 {\n\texpiresIn := b.GetConfig().ResultsExpireIn\n\tif expiresIn == 0 {\n\t\t// // expire results after 1 hour by default\n\t\texpiresIn = config.DefaultResultsExpireIn\n\t}\n\treturn int32(time.Now().Unix() + int64(expiresIn))\n}\n\n// getClient returns or creates instance of Memcache client\nfunc (b *Backend) getClient() *gomemcache.Client {\n\tif b.client == nil {\n\t\tb.client = gomemcache.New(b.servers...)\n\t}\n\treturn b.client\n}\n"
  },
  {
    "path": "v1/backends/memcache/memcache_test.go",
    "content": "package memcache_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/memcache\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGroupCompleted(t *testing.T) {\n\tmemcacheURL := os.Getenv(\"MEMCACHE_URL\")\n\tif memcacheURL == \"\" {\n\t\tt.Skip(\"MEMCACHE_URL is not defined\")\n\t}\n\n\tgroupUUID := \"testGroupUUID\"\n\ttask1 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID1\",\n\t\tGroupUUID: groupUUID,\n\t}\n\ttask2 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID2\",\n\t\tGroupUUID: groupUUID,\n\t}\n\n\tbackend := memcache.New(new(config.Config), []string{memcacheURL})\n\n\t// Cleanup before the test\n\tbackend.PurgeState(task1.UUID)\n\tbackend.PurgeState(task2.UUID)\n\tbackend.PurgeGroupMeta(groupUUID)\n\n\tgroupCompleted, err := backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"memcache: cache miss\", err.Error())\n\t}\n\n\tbackend.InitGroup(groupUUID, []string{task1.UUID, task2.UUID})\n\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"memcache: cache miss\", err.Error())\n\t}\n\n\tbackend.SetStatePending(task1)\n\tbackend.SetStateStarted(task2)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\ttaskResults := []*tasks.TaskResult{new(tasks.TaskResult)}\n\tbackend.SetStateStarted(task1)\n\tbackend.SetStateSuccess(task2, taskResults)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.SetStateFailure(task1, \"Some error\")\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, groupCompleted)\n\t}\n}\n\nfunc TestGetState(t *testing.T) {\n\tmemcacheURL := os.Getenv(\"MEMCACHE_URL\")\n\tif memcacheURL == \"\" {\n\t\tt.Skip(\"MEMCACHE_URL is not defined\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := memcache.New(new(config.Config), []string{memcacheURL})\n\n\tgo func() {\n\t\tbackend.SetStatePending(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\tbackend.SetStateReceived(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\tbackend.SetStateStarted(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\ttaskResults := []*tasks.TaskResult{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: 2,\n\t\t\t},\n\t\t}\n\t\tbackend.SetStateSuccess(signature, taskResults)\n\t}()\n\n\tvar (\n\t\ttaskState *tasks.TaskState\n\t\terr       error\n\t)\n\tfor {\n\t\ttaskState, err = backend.GetState(signature.UUID)\n\t\tif taskState == nil {\n\t\t\tassert.Equal(t, \"memcache: cache miss\", err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.NoError(t, err)\n\t\tif taskState.IsCompleted() {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc TestPurgeState(t *testing.T) {\n\tmemcacheURL := os.Getenv(\"MEMCACHE_URL\")\n\tif memcacheURL == \"\" {\n\t\tt.Skip(\"MEMCACHE_URL is not defined\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := memcache.New(new(config.Config), []string{memcacheURL})\n\n\tbackend.SetStatePending(signature)\n\ttaskState, err := backend.GetState(signature.UUID)\n\tassert.NotNil(t, taskState)\n\tassert.NoError(t, err)\n\n\tbackend.PurgeState(taskState.TaskUUID)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Nil(t, taskState)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "v1/backends/mongo/mongodb.go",
    "content": "package mongo\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.mongodb.org/mongo-driver/bson\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\t\"go.mongodb.org/mongo-driver/mongo/options\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// Backend represents a MongoDB result backend\ntype Backend struct {\n\tcommon.Backend\n\tclient *mongo.Client\n\ttc     *mongo.Collection\n\tgmc    *mongo.Collection\n\tonce   sync.Once\n}\n\n// New creates Backend instance\nfunc New(cnf *config.Config) (iface.Backend, error) {\n\tbackend := &Backend{\n\t\tBackend: common.NewBackend(cnf),\n\t\tonce:    sync.Once{},\n\t}\n\n\treturn backend, nil\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tgroupMeta := &tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n\t_, err := b.groupMetasCollection().InsertOne(context.Background(), groupMeta)\n\treturn err\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(groupMeta.TaskUUIDs...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn []*tasks.TaskState{}, err\n\t}\n\n\treturn b.getStates(groupMeta.TaskUUIDs...)\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never triggered multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\tquery := bson.M{\n\t\t\"_id\":             groupUUID,\n\t\t\"chord_triggered\": false,\n\t}\n\tchange := bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"chord_triggered\": true,\n\t\t},\n\t}\n\n\t_, err := b.groupMetasCollection().UpdateOne(context.Background(), query, change, options.Update())\n\n\tif err != nil {\n\t\tif err == mongo.ErrNoDocuments {\n\t\t\tlog.WARNING.Printf(\"Chord already triggered for group %s\", groupUUID)\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\tupdate := bson.M{\n\t\t\"state\":      tasks.StatePending,\n\t\t\"task_name\":  signature.Name,\n\t\t\"created_at\": time.Now().UTC(),\n\t}\n\treturn b.updateState(signature, update)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\tupdate := bson.M{\"state\": tasks.StateReceived}\n\treturn b.updateState(signature, update)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\tupdate := bson.M{\"state\": tasks.StateStarted}\n\treturn b.updateState(signature, update)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tupdate := bson.M{\"state\": tasks.StateRetry}\n\treturn b.updateState(signature, update)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\tdecodedResults := b.decodeResults(results)\n\tupdate := bson.M{\n\t\t\"state\":   tasks.StateSuccess,\n\t\t\"results\": decodedResults,\n\t}\n\treturn b.updateState(signature, update)\n}\n\n// decodeResults detects & decodes json strings in TaskResult.Value and returns a new slice\nfunc (b *Backend) decodeResults(results []*tasks.TaskResult) []*tasks.TaskResult {\n\tl := len(results)\n\tjsonResults := make([]*tasks.TaskResult, l)\n\tfor i, result := range results {\n\t\tjsonResult := new(bson.M)\n\t\tresultType := reflect.TypeOf(result.Value).Kind()\n\t\tif resultType == reflect.String {\n\t\t\terr := json.NewDecoder(strings.NewReader(result.Value.(string))).Decode(&jsonResult)\n\t\t\tif err == nil {\n\t\t\t\tjsonResults[i] = &tasks.TaskResult{\n\t\t\t\t\tType:  \"json\",\n\t\t\t\t\tValue: jsonResult,\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tjsonResults[i] = result\n\t}\n\treturn jsonResults\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\tupdate := bson.M{\"state\": tasks.StateFailure, \"error\": err}\n\treturn b.updateState(signature, update)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\tstate := &tasks.TaskState{}\n\terr := b.tasksCollection().FindOne(context.Background(), bson.M{\"_id\": taskUUID}).Decode(state)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\t_, err := b.tasksCollection().DeleteOne(context.Background(), bson.M{\"_id\": taskUUID})\n\treturn err\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\t_, err := b.groupMetasCollection().DeleteOne(context.Background(), bson.M{\"_id\": groupUUID})\n\treturn err\n}\n\n// lockGroupMeta acquires lock on groupUUID document\nfunc (b *Backend) lockGroupMeta(groupUUID string) error {\n\tquery := bson.M{\n\t\t\"_id\":  groupUUID,\n\t\t\"lock\": false,\n\t}\n\tchange := bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"lock\": true,\n\t\t},\n\t}\n\n\t_, err := b.groupMetasCollection().UpdateOne(context.Background(), query, change, options.Update().SetUpsert(true))\n\n\treturn err\n}\n\n// unlockGroupMeta releases lock on groupUUID document\nfunc (b *Backend) unlockGroupMeta(groupUUID string) error {\n\tupdate := bson.M{\"$set\": bson.M{\"lock\": false}}\n\t_, err := b.groupMetasCollection().UpdateOne(context.Background(), bson.M{\"_id\": groupUUID}, update, options.Update())\n\treturn err\n}\n\n// getGroupMeta retrieves group meta data, convenience function to avoid repetition\nfunc (b *Backend) getGroupMeta(groupUUID string) (*tasks.GroupMeta, error) {\n\tgroupMeta := &tasks.GroupMeta{}\n\tquery := bson.M{\"_id\": groupUUID}\n\n\terr := b.groupMetasCollection().FindOne(context.Background(), query).Decode(groupMeta)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn groupMeta, nil\n}\n\n// getStates returns multiple task states\nfunc (b *Backend) getStates(taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\tstates := make([]*tasks.TaskState, 0, len(taskUUIDs))\n\tcur, err := b.tasksCollection().Find(context.Background(), bson.M{\"_id\": bson.M{\"$in\": taskUUIDs}})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer cur.Close(context.Background())\n\n\tfor cur.Next(context.Background()) {\n\t\tstate := &tasks.TaskState{}\n\t\tif err := cur.Decode(state); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tstates = append(states, state)\n\t}\n\tif cur.Err() != nil {\n\t\treturn nil, err\n\t}\n\treturn states, nil\n}\n\n// updateState saves current task state\nfunc (b *Backend) updateState(signature *tasks.Signature, update bson.M) error {\n\tupdate = bson.M{\"$set\": update}\n\t_, err := b.tasksCollection().UpdateOne(context.Background(), bson.M{\"_id\": signature.UUID}, update, options.Update().SetUpsert(true))\n\treturn err\n}\n\nfunc (b *Backend) tasksCollection() *mongo.Collection {\n\tb.once.Do(func() {\n\t\tb.connect()\n\t})\n\n\treturn b.tc\n}\n\nfunc (b *Backend) groupMetasCollection() *mongo.Collection {\n\tb.once.Do(func() {\n\t\tb.connect()\n\t})\n\n\treturn b.gmc\n}\n\n// connect creates the underlying mgo connection if it doesn't exist\n// creates required indexes for our collections\nfunc (b *Backend) connect() error {\n\tclient, err := b.dial()\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.client = client\n\n\tdatabase := \"machinery\"\n\n\tif b.GetConfig().MongoDB != nil {\n\t\tdatabase = b.GetConfig().MongoDB.Database\n\t}\n\n\tb.tc = b.client.Database(database).Collection(\"tasks\")\n\tb.gmc = b.client.Database(database).Collection(\"group_metas\")\n\n\terr = b.createMongoIndexes(database)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// dial connects to mongo with TLSConfig if provided\n// else connects via ResultBackend uri\nfunc (b *Backend) dial() (*mongo.Client, error) {\n\n\tif b.GetConfig().MongoDB != nil && b.GetConfig().MongoDB.Client != nil {\n\t\treturn b.GetConfig().MongoDB.Client, nil\n\t}\n\n\turi := b.GetConfig().ResultBackend\n\tif strings.HasPrefix(uri, \"mongodb://\") == false &&\n\t\tstrings.HasPrefix(uri, \"mongodb+srv://\") == false {\n\t\turi = fmt.Sprintf(\"mongodb://%s\", uri)\n\t}\n\n\tclient, err := mongo.NewClient(options.Client().ApplyURI(uri))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\tif err := client.Connect(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn client, nil\n}\n\n// createMongoIndexes ensures all indexes are in place\nfunc (b *Backend) createMongoIndexes(database string) error {\n\n\ttasksCollection := b.client.Database(database).Collection(\"tasks\")\n\n\texpireIn := int32(b.GetConfig().ResultsExpireIn)\n\n\t_, err := tasksCollection.Indexes().CreateMany(context.Background(), []mongo.IndexModel{\n\t\t{\n\t\t\tKeys:    bson.M{\"state\": 1},\n\t\t\tOptions: options.Index().SetBackground(true).SetExpireAfterSeconds(expireIn),\n\t\t},\n\t\tmongo.IndexModel{\n\t\t\tKeys:    bson.M{\"lock\": 1},\n\t\t\tOptions: options.Index().SetBackground(true).SetExpireAfterSeconds(expireIn),\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "v1/backends/mongo/mongodb_test.go",
    "content": "package mongo_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/backends/mongo\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar (\n\tgroupUUID = \"123456\"\n\ttaskUUIDs = []string{\"1\", \"2\", \"3\"}\n)\n\nfunc newBackend() (iface.Backend, error) {\n\tcnf := &config.Config{\n\t\tResultBackend:   os.Getenv(\"MONGODB_URL\"),\n\t\tResultsExpireIn: 30,\n\t}\n\tbackend, err := mongo.New(cnf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbackend.PurgeGroupMeta(groupUUID)\n\tfor _, taskUUID := range taskUUIDs {\n\t\tbackend.PurgeState(taskUUID)\n\t}\n\n\tif err := backend.InitGroup(groupUUID, taskUUIDs); err != nil {\n\t\treturn nil, err\n\t}\n\treturn backend, nil\n}\n\nfunc TestNew(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif assert.NoError(t, err) {\n\t\tassert.NotNil(t, backend)\n\t}\n}\n\nfunc TestSetStatePending(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = backend.SetStatePending(&tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t})\n\tif assert.NoError(t, err) {\n\t\ttaskState, err := backend.GetState(taskUUIDs[0])\n\t\tif assert.NoError(t, err) {\n\t\t\tassert.Equal(t, tasks.StatePending, taskState.State, \"Not StatePending\")\n\t\t}\n\t}\n}\n\nfunc TestSetStateReceived(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = backend.SetStateReceived(&tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t})\n\tif assert.NoError(t, err) {\n\t\ttaskState, err := backend.GetState(taskUUIDs[0])\n\t\tif assert.NoError(t, err) {\n\t\t\tassert.Equal(t, tasks.StateReceived, taskState.State, \"Not StateReceived\")\n\t\t}\n\t}\n}\n\nfunc TestSetStateStarted(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = backend.SetStateStarted(&tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t})\n\tif assert.NoError(t, err) {\n\t\ttaskState, err := backend.GetState(taskUUIDs[0])\n\t\tif assert.NoError(t, err) {\n\t\t\tassert.Equal(t, tasks.StateStarted, taskState.State, \"Not StateStarted\")\n\t\t}\n\t}\n}\n\nfunc TestSetStateSuccess(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tresultType := \"float64\"\n\tresultValue := float64(88.5)\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t}\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  resultType,\n\t\t\tValue: resultValue,\n\t\t},\n\t}\n\terr = backend.SetStateSuccess(signature, taskResults)\n\tassert.NoError(t, err)\n\n\ttaskState, err := backend.GetState(taskUUIDs[0])\n\tassert.NoError(t, err)\n\tassert.Equal(t, tasks.StateSuccess, taskState.State, \"Not StateSuccess\")\n\tassert.Equal(t, resultType, taskState.Results[0].Type, \"Wrong result type\")\n\tassert.Equal(t, float64(resultValue), taskState.Results[0].Value.(float64), \"Wrong result value\")\n}\n\nfunc TestSetStateFailure(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tfailString := \"Fail is ok\"\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t}\n\terr = backend.SetStateFailure(signature, failString)\n\tassert.NoError(t, err)\n\n\ttaskState, err := backend.GetState(taskUUIDs[0])\n\tassert.NoError(t, err)\n\tassert.Equal(t, tasks.StateFailure, taskState.State, \"Not StateSuccess\")\n\tassert.Equal(t, failString, taskState.Error, \"Wrong fail error\")\n}\n\nfunc TestGroupCompleted(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttaskResultsState := make(map[string]string)\n\n\tisCompleted, err := backend.GroupCompleted(groupUUID, len(taskUUIDs))\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, isCompleted, \"Actually group is not completed\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t}\n\terr = backend.SetStateFailure(signature, \"Fail is ok\")\n\tassert.NoError(t, err)\n\ttaskResultsState[taskUUIDs[0]] = tasks.StateFailure\n\n\tsignature = &tasks.Signature{\n\t\tUUID: taskUUIDs[1],\n\t}\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  \"string\",\n\t\t\tValue: \"Result ok\",\n\t\t},\n\t}\n\terr = backend.SetStateSuccess(signature, taskResults)\n\tassert.NoError(t, err)\n\ttaskResultsState[taskUUIDs[1]] = tasks.StateSuccess\n\n\tsignature = &tasks.Signature{\n\t\tUUID: taskUUIDs[2],\n\t}\n\terr = backend.SetStateSuccess(signature, taskResults)\n\tassert.NoError(t, err)\n\ttaskResultsState[taskUUIDs[2]] = tasks.StateSuccess\n\n\tisCompleted, err = backend.GroupCompleted(groupUUID, len(taskUUIDs))\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, isCompleted, \"Actually group is completed\")\n\t}\n\n\ttaskStates, err := backend.GroupTaskStates(groupUUID, len(taskUUIDs))\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, len(taskStates), len(taskUUIDs), \"Wrong len tasksStates\")\n\tfor _, taskState := range taskStates {\n\t\tassert.Equal(\n\t\t\tt,\n\t\t\ttaskResultsState[taskState.TaskUUID],\n\t\t\ttaskState.State,\n\t\t\t\"Wrong state on\", taskState.TaskUUID,\n\t\t)\n\t}\n}\n\nfunc TestGroupStates(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttaskStates, err := backend.GroupTaskStates(groupUUID, len(taskUUIDs))\n\tassert.NoError(t, err)\n\tfor i, taskState := range taskStates {\n\t\tassert.Equal(t, taskUUIDs[i], taskState.TaskUUID)\n\t}\n}\n"
  },
  {
    "path": "v1/backends/null/null.go",
    "content": "package null\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// ErrGroupNotFound ...\ntype ErrGroupNotFound struct {\n\tgroupUUID string\n}\n\n// NewErrGroupNotFound returns new instance of ErrGroupNotFound\nfunc NewErrGroupNotFound(groupUUID string) ErrGroupNotFound {\n\treturn ErrGroupNotFound{groupUUID: groupUUID}\n}\n\n// Error implements error interface\nfunc (e ErrGroupNotFound) Error() string {\n\treturn fmt.Sprintf(\"Group not found: %v\", e.groupUUID)\n}\n\n// ErrTasknotFound ...\ntype ErrTasknotFound struct {\n\ttaskUUID string\n}\n\n// NewErrTasknotFound returns new instance of ErrTasknotFound\nfunc NewErrTasknotFound(taskUUID string) ErrTasknotFound {\n\treturn ErrTasknotFound{taskUUID: taskUUID}\n}\n\n// Error implements error interface\nfunc (e ErrTasknotFound) Error() string {\n\treturn fmt.Sprintf(\"Task not found: %v\", e.taskUUID)\n}\n\n// Backend represents an \"null\" result backend\ntype Backend struct {\n\tcommon.Backend\n\tgroups map[string]struct{}\n}\n\n// New creates NullBackend instance\nfunc New() iface.Backend {\n\treturn &Backend{\n\t\tBackend: common.NewBackend(new(config.Config)),\n\t\tgroups:  make(map[string]struct{}),\n\t}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tb.groups[groupUUID] = struct{}{}\n\treturn nil\n}\n\n// GroupCompleted returns true (always)\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\t_, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn false, NewErrGroupNotFound(groupUUID)\n\t}\n\n\treturn true, nil\n}\n\n// GroupTaskStates returns null states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\t_, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn nil, NewErrGroupNotFound(groupUUID)\n\t}\n\n\tret := make([]*tasks.TaskState, 0, groupTaskCount)\n\treturn ret, nil\n}\n\n// TriggerChord returns true (always)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\treturn true, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\tstate := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\tstate := tasks.NewReceivedTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\tstate := tasks.NewStartedTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tstate := tasks.NewRetryTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\tstate := tasks.NewSuccessTaskState(signature, results)\n\treturn b.updateState(state)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\tstate := tasks.NewFailureTaskState(signature, err)\n\treturn b.updateState(state)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\treturn nil, NewErrTasknotFound(taskUUID)\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\treturn NewErrTasknotFound(taskUUID)\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\t_, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn NewErrGroupNotFound(groupUUID)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Backend) updateState(s *tasks.TaskState) error {\n\treturn nil\n}\n"
  },
  {
    "path": "v1/backends/package.go",
    "content": "package backends\n"
  },
  {
    "path": "v1/backends/redis/goredis.go",
    "content": "package redis\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-redsync/redsync/v4\"\n\tredsyncgoredis \"github.com/go-redsync/redsync/v4/redis/goredis/v9\"\n\t\"github.com/redis/go-redis/v9\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// BackendGR represents a Redis result backend\ntype BackendGR struct {\n\tcommon.Backend\n\trclient  redis.UniversalClient\n\thost     string\n\tpassword string\n\tdb       int\n\t// If set, path to a socket file overrides hostname\n\tsocketPath string\n\tredsync    *redsync.Redsync\n\tredisOnce  sync.Once\n}\n\n// NewGR creates Backend instance\nfunc NewGR(cnf *config.Config, addrs []string, db int) iface.Backend {\n\tb := &BackendGR{\n\t\tBackend: common.NewBackend(cnf),\n\t}\n\tvar password string\n\tvar username string\n\tparts := strings.Split(addrs[0], \"@\")\n\tif len(parts) >= 2 {\n\t\t// with password\n\t\toptions := strings.SplitN(strings.Join(parts[:len(parts)-1], \"@\"), \":\", 2)\n\t\tif len(options) >= 2 {\n\t\t\tusername = options[0]\n\t\t\tpassword = options[1]\n\t\t} else {\n\t\t\tpassword = options[0]\n\t\t}\n\n\t\taddrs[0] = parts[len(parts)-1] // addr is the last one without @\n\t}\n\n\tropt := &redis.UniversalOptions{\n\t\tAddrs:    addrs,\n\t\tDB:       db,\n\t\tPassword: password,\n\t\tUsername: username,\n\t}\n\tif cnf.Redis != nil {\n\t\tropt.MasterName = cnf.Redis.MasterName\n\t}\n\n\tif cnf.Redis != nil && cnf.Redis.ClusterMode {\n\t\tb.rclient = redis.NewClusterClient(ropt.Cluster())\n\t} else {\n\n\t\tb.rclient = redis.NewUniversalClient(ropt)\n\t}\n\tb.redsync = redsync.New(redsyncgoredis.NewPool(b.rclient))\n\treturn b\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *BackendGR) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tgroupMeta := &tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n\n\tencoded, err := json.Marshal(groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texpiration := b.getExpiration()\n\terr = b.rclient.Set(context.Background(), groupUUID, encoded, expiration).Err()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *BackendGR) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(groupMeta.TaskUUIDs...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *BackendGR) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn []*tasks.TaskState{}, err\n\t}\n\n\treturn b.getStates(groupMeta.TaskUUIDs...)\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *BackendGR) TriggerChord(groupUUID string) (bool, error) {\n\tm := b.redsync.NewMutex(\"TriggerChordMutex\")\n\tif err := m.Lock(); err != nil {\n\t\treturn false, err\n\t}\n\tdefer m.Unlock()\n\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Chord has already been triggered, return false (should not trigger again)\n\tif groupMeta.ChordTriggered {\n\t\treturn false, nil\n\t}\n\n\t// Set flag to true\n\tgroupMeta.ChordTriggered = true\n\n\t// Update the group meta\n\tencoded, err := json.Marshal(&groupMeta)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\texpiration := b.getExpiration()\n\terr = b.rclient.Set(context.Background(), groupUUID, encoded, expiration).Err()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (b *BackendGR) mergeNewTaskState(newState *tasks.TaskState) {\n\tstate, err := b.GetState(newState.TaskUUID)\n\tif err == nil {\n\t\tnewState.CreatedAt = state.CreatedAt\n\t\tnewState.TaskName = state.TaskName\n\t}\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *BackendGR) SetStatePending(signature *tasks.Signature) error {\n\ttaskState := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *BackendGR) SetStateReceived(signature *tasks.Signature) error {\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *BackendGR) SetStateStarted(signature *tasks.Signature) error {\n\ttaskState := tasks.NewStartedTaskState(signature)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *BackendGR) SetStateRetry(signature *tasks.Signature) error {\n\ttaskState := tasks.NewRetryTaskState(signature)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *BackendGR) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *BackendGR) SetStateFailure(signature *tasks.Signature, err string) error {\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// GetState returns the latest task state\nfunc (b *BackendGR) GetState(taskUUID string) (*tasks.TaskState, error) {\n\n\titem, err := b.rclient.Get(context.Background(), taskUUID).Bytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader(item))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *BackendGR) PurgeState(taskUUID string) error {\n\terr := b.rclient.Del(context.Background(), taskUUID).Err()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *BackendGR) PurgeGroupMeta(groupUUID string) error {\n\terr := b.rclient.Del(context.Background(), groupUUID).Err()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getGroupMeta retrieves group meta data, convenience function to avoid repetition\nfunc (b *BackendGR) getGroupMeta(groupUUID string) (*tasks.GroupMeta, error) {\n\titem, err := b.rclient.Get(context.Background(), groupUUID).Bytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroupMeta := new(tasks.GroupMeta)\n\tdecoder := json.NewDecoder(bytes.NewReader(item))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(groupMeta); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn groupMeta, nil\n}\n\n// getStates returns multiple task states\nfunc (b *BackendGR) getStates(taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\ttaskStates := make([]*tasks.TaskState, len(taskUUIDs))\n\t// to avoid CROSSSLOT error, use pipeline\n\tcmders, err := b.rclient.Pipelined(context.Background(), func(pipeliner redis.Pipeliner) error {\n\t\tfor _, uuid := range taskUUIDs {\n\t\t\tpipeliner.Get(context.Background(), uuid)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn taskStates, err\n\t}\n\tfor i, cmder := range cmders {\n\t\tstateBytes, err1 := cmder.(*redis.StringCmd).Bytes()\n\t\tif err1 != nil {\n\t\t\treturn taskStates, err1\n\t\t}\n\t\ttaskState := new(tasks.TaskState)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(stateBytes))\n\t\tdecoder.UseNumber()\n\t\tif err1 = decoder.Decode(taskState); err1 != nil {\n\t\t\tlog.ERROR.Print(err1)\n\t\t\treturn taskStates, err1\n\t\t}\n\t\ttaskStates[i] = taskState\n\t}\n\n\treturn taskStates, nil\n}\n\n// updateState saves current task state\nfunc (b *BackendGR) updateState(taskState *tasks.TaskState) error {\n\tencoded, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texpiration := b.getExpiration()\n\t_, err = b.rclient.Set(context.Background(), taskState.TaskUUID, encoded, expiration).Result()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getExpiration returns expiration for a stored task state\nfunc (b *BackendGR) getExpiration() time.Duration {\n\texpiresIn := b.GetConfig().ResultsExpireIn\n\tif expiresIn == 0 {\n\t\t// expire results after 1 hour by default\n\t\texpiresIn = config.DefaultResultsExpireIn\n\t}\n\n\treturn time.Duration(expiresIn) * time.Second\n}\n"
  },
  {
    "path": "v1/backends/redis/goredis_test.go",
    "content": "package redis_test\n\nimport (\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/redis\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc getRedisG() iface.Backend {\n\t// host1:port1,host2:port2\n\tredisURL := os.Getenv(\"REDIS_URL_GR\")\n\t//redisPassword := os.Getenv(\"REDIS_PASSWORD\")\n\tif redisURL == \"\" {\n\t\treturn nil\n\t}\n\tbackend := redis.NewGR(new(config.Config), strings.Split(redisURL, \",\"), 0)\n\treturn backend\n}\n\nfunc TestGroupCompletedGR(t *testing.T) {\n\tbackend := getRedisG()\n\tif backend == nil {\n\t\tt.Skip()\n\t}\n\n\tgroupUUID := \"testGroupUUID\"\n\ttask1 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID1\",\n\t\tGroupUUID: groupUUID,\n\t}\n\ttask2 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID2\",\n\t\tGroupUUID: groupUUID,\n\t}\n\n\t// Cleanup before the test\n\tbackend.PurgeState(task1.UUID)\n\tbackend.PurgeState(task2.UUID)\n\tbackend.PurgeGroupMeta(groupUUID)\n\n\tgroupCompleted, err := backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"redis: nil\", err.Error())\n\t}\n\n\tbackend.InitGroup(groupUUID, []string{task1.UUID, task2.UUID})\n\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"redis: nil\", err.Error())\n\t}\n\n\tbackend.SetStatePending(task1)\n\tbackend.SetStateStarted(task2)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\ttaskResults := []*tasks.TaskResult{new(tasks.TaskResult)}\n\tbackend.SetStateStarted(task1)\n\tbackend.SetStateSuccess(task2, taskResults)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.SetStateFailure(task1, \"Some error\")\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, groupCompleted)\n\t}\n}\n\nfunc TestGetStateGR(t *testing.T) {\n\tbackend := getRedisG()\n\tif backend == nil {\n\t\tt.Skip()\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend.PurgeState(\"testTaskUUID\")\n\n\tvar (\n\t\ttaskState *tasks.TaskState\n\t\terr       error\n\t)\n\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Equal(t, \"redis: nil\", err.Error())\n\tassert.Nil(t, taskState)\n\n\t//Pending State\n\tbackend.SetStatePending(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tcreatedAt := taskState.CreatedAt\n\n\t//Received State\n\tbackend.SetStateReceived(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\n\t//Started State\n\tbackend.SetStateStarted(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\n\t//Success State\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  \"float64\",\n\t\t\tValue: 2,\n\t\t},\n\t}\n\tbackend.SetStateSuccess(signature, taskResults)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\tassert.NotNil(t, taskState.Results)\n}\n\nfunc TestPurgeStateGR(t *testing.T) {\n\tbackend := getRedisG()\n\tif backend == nil {\n\t\tt.Skip()\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend.SetStatePending(signature)\n\ttaskState, err := backend.GetState(signature.UUID)\n\tassert.NotNil(t, taskState)\n\tassert.NoError(t, err)\n\n\tbackend.PurgeState(taskState.TaskUUID)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Nil(t, taskState)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "v1/backends/redis/redis.go",
    "content": "package redis\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-redsync/redsync/v4\"\n\tredsyncredis \"github.com/go-redsync/redsync/v4/redis/redigo\"\n\t\"github.com/gomodule/redigo/redis\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// Backend represents a Redis result backend\ntype Backend struct {\n\tcommon.Backend\n\thost     string\n\tusername string\n\tpassword string\n\tdb       int\n\tpool     *redis.Pool\n\t// If set, path to a socket file overrides hostname\n\tsocketPath string\n\tredsync    *redsync.Redsync\n\tredisOnce  sync.Once\n\tcommon.RedisConnector\n}\n\n// New creates Backend instance\nfunc New(cnf *config.Config, host, username, password, socketPath string, db int) iface.Backend {\n\treturn &Backend{\n\t\tBackend:    common.NewBackend(cnf),\n\t\thost:       host,\n\t\tdb:         db,\n\t\tusername:   username,\n\t\tpassword:   password,\n\t\tsocketPath: socketPath,\n\t}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tgroupMeta := &tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n\n\tencoded, err := json.Marshal(groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconn := b.open()\n\tdefer conn.Close()\n\n\texpiration := int64(b.getExpiration().Seconds())\n\t_, err = conn.Do(\"SET\", groupUUID, encoded, \"EX\", expiration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tgroupMeta, err := b.getGroupMeta(conn, groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(conn, groupMeta.TaskUUIDs...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tgroupMeta, err := b.getGroupMeta(conn, groupUUID)\n\tif err != nil {\n\t\treturn []*tasks.TaskState{}, err\n\t}\n\n\treturn b.getStates(conn, groupMeta.TaskUUIDs...)\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tm := b.redsync.NewMutex(\"TriggerChordMutex\")\n\tif err := m.Lock(); err != nil {\n\t\treturn false, err\n\t}\n\tdefer m.Unlock()\n\n\tgroupMeta, err := b.getGroupMeta(conn, groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Chord has already been triggered, return false (should not trigger again)\n\tif groupMeta.ChordTriggered {\n\t\treturn false, nil\n\t}\n\n\t// Set flag to true\n\tgroupMeta.ChordTriggered = true\n\n\t// Update the group meta\n\tencoded, err := json.Marshal(&groupMeta)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\texpiration := int64(b.getExpiration().Seconds())\n\t_, err = conn.Do(\"SET\", groupUUID, encoded, \"EX\", expiration)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (b *Backend) mergeNewTaskState(conn redis.Conn, newState *tasks.TaskState) {\n\tstate, err := b.getState(conn, newState.TaskUUID)\n\tif err == nil {\n\t\tnewState.CreatedAt = state.CreatedAt\n\t\tnewState.TaskName = state.TaskName\n\t}\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewStartedTaskState(signature)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewRetryTaskState(signature)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\treturn b.getState(conn, taskUUID)\n}\n\nfunc (b *Backend) getState(conn redis.Conn, taskUUID string) (*tasks.TaskState, error) {\n\titem, err := redis.Bytes(conn.Do(\"GET\", taskUUID))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader(item))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\t_, err := conn.Do(\"DEL\", taskUUID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\t_, err := conn.Do(\"DEL\", groupUUID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getGroupMeta retrieves group meta data, convenience function to avoid repetition\nfunc (b *Backend) getGroupMeta(conn redis.Conn, groupUUID string) (*tasks.GroupMeta, error) {\n\n\titem, err := redis.Bytes(conn.Do(\"GET\", groupUUID))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroupMeta := new(tasks.GroupMeta)\n\tdecoder := json.NewDecoder(bytes.NewReader(item))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(groupMeta); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn groupMeta, nil\n}\n\n// getStates returns multiple task states\nfunc (b *Backend) getStates(conn redis.Conn, taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\ttaskStates := make([]*tasks.TaskState, len(taskUUIDs))\n\n\t// conn.Do requires []interface{}... can't pass []string unfortunately\n\ttaskUUIDInterfaces := make([]interface{}, len(taskUUIDs))\n\tfor i, taskUUID := range taskUUIDs {\n\t\ttaskUUIDInterfaces[i] = interface{}(taskUUID)\n\t}\n\n\treply, err := redis.Values(conn.Do(\"MGET\", taskUUIDInterfaces...))\n\tif err != nil {\n\t\treturn taskStates, err\n\t}\n\n\tfor i, value := range reply {\n\t\tstateBytes, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn taskStates, fmt.Errorf(\"Expected byte array, instead got: %v\", value)\n\t\t}\n\n\t\ttaskState := new(tasks.TaskState)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(stateBytes))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(taskState); err != nil {\n\t\t\tlog.ERROR.Print(err)\n\t\t\treturn taskStates, err\n\t\t}\n\n\t\ttaskStates[i] = taskState\n\t}\n\n\treturn taskStates, nil\n}\n\n// updateState saves current task state\nfunc (b *Backend) updateState(conn redis.Conn, taskState *tasks.TaskState) error {\n\tencoded, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texpiration := int64(b.getExpiration().Seconds())\n\t_, err = conn.Do(\"SET\", taskState.TaskUUID, encoded, \"EX\", expiration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getExpiration returns expiration for a stored task state\nfunc (b *Backend) getExpiration() time.Duration {\n\texpiresIn := b.GetConfig().ResultsExpireIn\n\tif expiresIn == 0 {\n\t\t// expire results after 1 hour by default\n\t\texpiresIn = config.DefaultResultsExpireIn\n\t}\n\n\treturn time.Duration(expiresIn) * time.Second\n}\n\n// open returns or creates instance of Redis connection\nfunc (b *Backend) open() redis.Conn {\n\tb.redisOnce.Do(func() {\n\t\tb.pool = b.NewPool(b.socketPath, b.host, b.username, b.password, b.db, b.GetConfig().Redis, b.GetConfig().TLSConfig)\n\t\tb.redsync = redsync.New(redsyncredis.NewPool(b.pool))\n\t})\n\treturn b.pool.Get()\n}\n"
  },
  {
    "path": "v1/backends/redis/redis_test.go",
    "content": "package redis_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/redis\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGroupCompleted(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tredisPassword := os.Getenv(\"REDIS_PASSWORD\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\tgroupUUID := \"testGroupUUID\"\n\ttask1 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID1\",\n\t\tGroupUUID: groupUUID,\n\t}\n\ttask2 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID2\",\n\t\tGroupUUID: groupUUID,\n\t}\n\n\tbackend := redis.New(new(config.Config), redisURL, \"\", redisPassword, \"\", 0)\n\n\t// Cleanup before the test\n\tbackend.PurgeState(task1.UUID)\n\tbackend.PurgeState(task2.UUID)\n\tbackend.PurgeGroupMeta(groupUUID)\n\n\tgroupCompleted, err := backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"redigo: nil returned\", err.Error())\n\t}\n\n\tbackend.InitGroup(groupUUID, []string{task1.UUID, task2.UUID})\n\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"Expected byte array, instead got: <nil>\", err.Error())\n\t}\n\n\tbackend.SetStatePending(task1)\n\tbackend.SetStateStarted(task2)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\ttaskResults := []*tasks.TaskResult{new(tasks.TaskResult)}\n\tbackend.SetStateStarted(task1)\n\tbackend.SetStateSuccess(task2, taskResults)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.SetStateFailure(task1, \"Some error\")\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, groupCompleted)\n\t}\n}\n\nfunc TestGetState(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tredisPassword := os.Getenv(\"REDIS_PASSWORD\")\n\tif redisURL == \"\" {\n\t\treturn\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := redis.New(new(config.Config), redisURL, \"\", redisPassword, \"\", 0)\n\n\tbackend.PurgeState(\"testTaskUUID\")\n\n\tvar (\n\t\ttaskState *tasks.TaskState\n\t\terr       error\n\t)\n\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Equal(t, \"redigo: nil returned\", err.Error())\n\tassert.Nil(t, taskState)\n\n\t//Pending State\n\tbackend.SetStatePending(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tcreatedAt := taskState.CreatedAt\n\n\t//Received State\n\tbackend.SetStateReceived(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\n\t//Started State\n\tbackend.SetStateStarted(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\n\t//Success State\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  \"float64\",\n\t\t\tValue: 2,\n\t\t},\n\t}\n\tbackend.SetStateSuccess(signature, taskResults)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\tassert.NotNil(t, taskState.Results)\n}\n\nfunc TestPurgeState(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tredisPassword := os.Getenv(\"REDIS_PASSWORD\")\n\tif redisURL == \"\" {\n\t\treturn\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := redis.New(new(config.Config), redisURL, \"\", redisPassword, \"\", 0)\n\n\tbackend.SetStatePending(signature)\n\ttaskState, err := backend.GetState(signature.UUID)\n\tassert.NotNil(t, taskState)\n\tassert.NoError(t, err)\n\n\tbackend.PurgeState(taskState.TaskUUID)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Nil(t, taskState)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "v1/backends/result/async_result.go",
    "content": "package result\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\nvar (\n\t// ErrBackendNotConfigured ...\n\tErrBackendNotConfigured = errors.New(\"Result backend not configured\")\n\t// ErrTimeoutReached ...\n\tErrTimeoutReached = errors.New(\"Timeout reached\")\n)\n\n// AsyncResult represents a task result\ntype AsyncResult struct {\n\tSignature *tasks.Signature\n\ttaskState *tasks.TaskState\n\tbackend   iface.Backend\n}\n\n// ChordAsyncResult represents a result of a chord\ntype ChordAsyncResult struct {\n\tgroupAsyncResults []*AsyncResult\n\tchordAsyncResult  *AsyncResult\n\tbackend           iface.Backend\n}\n\n// ChainAsyncResult represents a result of a chain of tasks\ntype ChainAsyncResult struct {\n\tasyncResults []*AsyncResult\n\tbackend      iface.Backend\n}\n\n// NewAsyncResult creates AsyncResult instance\nfunc NewAsyncResult(signature *tasks.Signature, backend iface.Backend) *AsyncResult {\n\treturn &AsyncResult{\n\t\tSignature: signature,\n\t\ttaskState: new(tasks.TaskState),\n\t\tbackend:   backend,\n\t}\n}\n\n// NewChordAsyncResult creates ChordAsyncResult instance\nfunc NewChordAsyncResult(groupTasks []*tasks.Signature, chordCallback *tasks.Signature, backend iface.Backend) *ChordAsyncResult {\n\tasyncResults := make([]*AsyncResult, len(groupTasks))\n\tfor i, task := range groupTasks {\n\t\tasyncResults[i] = NewAsyncResult(task, backend)\n\t}\n\treturn &ChordAsyncResult{\n\t\tgroupAsyncResults: asyncResults,\n\t\tchordAsyncResult:  NewAsyncResult(chordCallback, backend),\n\t\tbackend:           backend,\n\t}\n}\n\n// NewChainAsyncResult creates ChainAsyncResult instance\nfunc NewChainAsyncResult(tasks []*tasks.Signature, backend iface.Backend) *ChainAsyncResult {\n\tasyncResults := make([]*AsyncResult, len(tasks))\n\tfor i, task := range tasks {\n\t\tasyncResults[i] = NewAsyncResult(task, backend)\n\t}\n\treturn &ChainAsyncResult{\n\t\tasyncResults: asyncResults,\n\t\tbackend:      backend,\n\t}\n}\n\n// Touch the state and don't wait\nfunc (asyncResult *AsyncResult) Touch() ([]reflect.Value, error) {\n\tif asyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tasyncResult.GetState()\n\n\t// Purge state if we are using AMQP backend\n\tif asyncResult.backend.IsAMQP() && asyncResult.taskState.IsCompleted() {\n\t\tasyncResult.backend.PurgeState(asyncResult.taskState.TaskUUID)\n\t}\n\n\tif asyncResult.taskState.IsFailure() {\n\t\treturn nil, errors.New(asyncResult.taskState.Error)\n\t}\n\n\tif asyncResult.taskState.IsSuccess() {\n\t\treturn tasks.ReflectTaskResults(asyncResult.taskState.Results)\n\t}\n\n\treturn nil, nil\n}\n\n// Get returns task results (synchronous blocking call)\nfunc (asyncResult *AsyncResult) Get(sleepDuration time.Duration) ([]reflect.Value, error) {\n\tfor {\n\t\tresults, err := asyncResult.Touch()\n\n\t\tif results == nil && err == nil {\n\t\t\ttime.Sleep(sleepDuration)\n\t\t} else {\n\t\t\treturn results, err\n\t\t}\n\t}\n}\n\n// GetWithTimeout returns task results with a timeout (synchronous blocking call)\nfunc (asyncResult *AsyncResult) GetWithTimeout(timeoutDuration, sleepDuration time.Duration) ([]reflect.Value, error) {\n\ttimeout := time.NewTimer(timeoutDuration)\n\n\tfor {\n\t\tselect {\n\t\tcase <-timeout.C:\n\t\t\treturn nil, ErrTimeoutReached\n\t\tdefault:\n\t\t\tresults, err := asyncResult.Touch()\n\n\t\t\tif results == nil && err == nil {\n\t\t\t\ttime.Sleep(sleepDuration)\n\t\t\t} else {\n\t\t\t\treturn results, err\n\t\t\t}\n\t\t}\n\t}\n}\n\n// GetState returns latest task state\nfunc (asyncResult *AsyncResult) GetState() *tasks.TaskState {\n\tif asyncResult.taskState.IsCompleted() {\n\t\treturn asyncResult.taskState\n\t}\n\n\ttaskState, err := asyncResult.backend.GetState(asyncResult.Signature.UUID)\n\tif err == nil {\n\t\tasyncResult.taskState = taskState\n\t}\n\n\treturn asyncResult.taskState\n}\n\n// Get returns results of a chain of tasks (synchronous blocking call)\nfunc (chainAsyncResult *ChainAsyncResult) Get(sleepDuration time.Duration) ([]reflect.Value, error) {\n\tif chainAsyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tvar (\n\t\tresults []reflect.Value\n\t\terr     error\n\t)\n\n\tfor _, asyncResult := range chainAsyncResult.asyncResults {\n\t\tresults, err = asyncResult.Get(sleepDuration)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn results, err\n}\n\n// Get returns result of a chord (synchronous blocking call)\nfunc (chordAsyncResult *ChordAsyncResult) Get(sleepDuration time.Duration) ([]reflect.Value, error) {\n\tif chordAsyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tvar err error\n\tfor _, asyncResult := range chordAsyncResult.groupAsyncResults {\n\t\t_, err = asyncResult.Get(sleepDuration)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn chordAsyncResult.chordAsyncResult.Get(sleepDuration)\n}\n\n// GetWithTimeout returns results of a chain of tasks with timeout (synchronous blocking call)\nfunc (chainAsyncResult *ChainAsyncResult) GetWithTimeout(timeoutDuration, sleepDuration time.Duration) ([]reflect.Value, error) {\n\tif chainAsyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tvar (\n\t\tresults []reflect.Value\n\t\terr     error\n\t)\n\n\ttimeout := time.NewTimer(timeoutDuration)\n\tln := len(chainAsyncResult.asyncResults)\n\tlastResult := chainAsyncResult.asyncResults[ln-1]\n\n\tfor {\n\t\tselect {\n\t\tcase <-timeout.C:\n\t\t\treturn nil, ErrTimeoutReached\n\t\tdefault:\n\n\t\t\tfor _, asyncResult := range chainAsyncResult.asyncResults {\n\t\t\t\t_, err = asyncResult.Touch()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults, err = lastResult.Touch()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif results != nil {\n\t\t\t\treturn results, err\n\t\t\t}\n\t\t\ttime.Sleep(sleepDuration)\n\t\t}\n\t}\n}\n\n// GetWithTimeout returns result of a chord with a timeout (synchronous blocking call)\nfunc (chordAsyncResult *ChordAsyncResult) GetWithTimeout(timeoutDuration, sleepDuration time.Duration) ([]reflect.Value, error) {\n\tif chordAsyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tvar (\n\t\tresults []reflect.Value\n\t\terr     error\n\t)\n\n\ttimeout := time.NewTimer(timeoutDuration)\n\tfor {\n\t\tselect {\n\t\tcase <-timeout.C:\n\t\t\treturn nil, ErrTimeoutReached\n\t\tdefault:\n\t\t\tfor _, asyncResult := range chordAsyncResult.groupAsyncResults {\n\t\t\t\t_, errcur := asyncResult.Touch()\n\t\t\t\tif errcur != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults, err = chordAsyncResult.chordAsyncResult.Touch()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\tif results != nil {\n\t\t\t\treturn results, err\n\t\t\t}\n\t\t\ttime.Sleep(sleepDuration)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "v1/brokers/amqp/amqp.go",
    "content": "package amqp\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/pkg/errors\"\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\ntype AMQPConnection struct {\n\tqueueName    string\n\tconnection   *amqp.Connection\n\tchannel      *amqp.Channel\n\tqueue        amqp.Queue\n\tconfirmation <-chan amqp.Confirmation\n\terrorchan    <-chan *amqp.Error\n\tcleanup      chan struct{}\n}\n\n// Broker represents an AMQP broker\ntype Broker struct {\n\tcommon.Broker\n\tcommon.AMQPConnector\n\tprocessingWG sync.WaitGroup // use wait group to make sure task processing completes on interrupt signal\n\n\tconnections      map[string]*AMQPConnection\n\tconnectionsMutex sync.RWMutex\n}\n\n// New creates new Broker instance\nfunc New(cnf *config.Config) iface.Broker {\n\treturn &Broker{Broker: common.NewBroker(cnf), AMQPConnector: common.AMQPConnector{}, connections: make(map[string]*AMQPConnection)}\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\n\tqueueName := taskProcessor.CustomQueue()\n\tif queueName == \"\" {\n\t\tqueueName = b.GetConfig().DefaultQueue\n\t}\n\n\tconn, channel, queue, _, amqpCloseChan, err := b.Connect(\n\t\tb.GetConfig().Broker,\n\t\tb.GetConfig().MultipleBrokerSeparator,\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\tqueueName,                       // queue name\n\t\ttrue,                            // queue durable\n\t\tfalse,                           // queue delete when unused\n\t\tb.GetConfig().AMQP.BindingKey,   // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueDeclareArgs), // queue declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueBindingArgs), // queue binding args\n\t)\n\tif err != nil {\n\t\tb.GetRetryFunc()(b.GetRetryStopChan())\n\t\treturn b.GetRetry(), err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tif err = channel.Qos(\n\t\tb.GetConfig().AMQP.PrefetchCount,\n\t\t0,     // prefetch size\n\t\tfalse, // global\n\t); err != nil {\n\t\treturn b.GetRetry(), fmt.Errorf(\"Channel qos error: %s\", err)\n\t}\n\n\tdeliveries, err := channel.Consume(\n\t\tqueue.Name,  // queue\n\t\tconsumerTag, // consumer tag\n\t\tfalse,       // auto-ack\n\t\tfalse,       // exclusive\n\t\tfalse,       // no-local\n\t\tfalse,       // no-wait\n\t\tnil,         // arguments\n\t)\n\tif err != nil {\n\t\treturn b.GetRetry(), fmt.Errorf(\"Queue consume error: %s\", err)\n\t}\n\n\tlog.INFO.Print(\"[*] Waiting for messages. To exit press CTRL+C\")\n\n\tif err := b.consume(deliveries, concurrency, taskProcessor, amqpCloseChan); err != nil {\n\t\treturn b.GetRetry(), err\n\t}\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *Broker) StopConsuming() {\n\tb.Broker.StopConsuming()\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n}\n\n// GetOrOpenConnection will return a connection on a particular queue name. Open connections\n// are saved to avoid having to reopen connection for multiple queues\nfunc (b *Broker) GetOrOpenConnection(queueName string, queueBindingKey string, exchangeDeclareArgs, queueDeclareArgs, queueBindingArgs amqp.Table) (*AMQPConnection, error) {\n\tvar err error\n\n\tb.connectionsMutex.Lock()\n\tdefer b.connectionsMutex.Unlock()\n\n\tconn, ok := b.connections[queueName]\n\tif !ok {\n\t\tconn = &AMQPConnection{\n\t\t\tqueueName: queueName,\n\t\t\tcleanup:   make(chan struct{}),\n\t\t}\n\t\tconn.connection, conn.channel, conn.queue, conn.confirmation, conn.errorchan, err = b.Connect(\n\t\t\tb.GetConfig().Broker,\n\t\t\tb.GetConfig().MultipleBrokerSeparator,\n\t\t\tb.GetConfig().TLSConfig,\n\t\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\t\tqueueName,                       // queue name\n\t\t\ttrue,                            // queue durable\n\t\t\tfalse,                           // queue delete when unused\n\t\t\tqueueBindingKey,                 // queue binding key\n\t\t\texchangeDeclareArgs,             // exchange declare args\n\t\t\tqueueDeclareArgs,                // queue declare args\n\t\t\tqueueBindingArgs,                // queue binding args\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrapf(err, \"Failed to connect to queue %s\", queueName)\n\t\t}\n\n\t\t// Reconnect to the channel if it disconnects/errors out\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase err = <-conn.errorchan:\n\t\t\t\tlog.INFO.Printf(\"Error occurred on queue: %s. Reconnecting\", queueName)\n\t\t\t\tb.connectionsMutex.Lock()\n\t\t\t\tdelete(b.connections, queueName)\n\t\t\t\tb.connectionsMutex.Unlock()\n\t\t\t\t_, err := b.GetOrOpenConnection(queueName, queueBindingKey, exchangeDeclareArgs, queueDeclareArgs, queueBindingArgs)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.ERROR.Printf(\"Failed to reopen queue: %s.\", queueName)\n\t\t\t\t}\n\t\t\tcase <-conn.cleanup:\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\t\tb.connections[queueName] = conn\n\t}\n\treturn conn, nil\n}\n\nfunc (b *Broker) CloseConnections() error {\n\tb.connectionsMutex.Lock()\n\tdefer b.connectionsMutex.Unlock()\n\n\tfor key, conn := range b.connections {\n\t\tif err := b.Close(conn.channel, conn.connection); err != nil {\n\t\t\tlog.ERROR.Print(\"Failed to close channel\")\n\t\t\treturn nil\n\t\t}\n\t\tclose(conn.cleanup)\n\t\tdelete(b.connections, key)\n\t}\n\treturn nil\n}\n\n// Publish places a new message on the default queue\nfunc (b *Broker) Publish(ctx context.Context, signature *tasks.Signature) error {\n\t// Adjust routing key (this decides which queue the message will be published to)\n\tb.AdjustRoutingKey(signature)\n\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// delay the task\n\tif signature.ETA != nil {\n\t\tnow := time.Now().UTC()\n\n\t\tif signature.ETA.After(now) {\n\t\t\tdelayMs := int64(signature.ETA.Sub(now) / time.Millisecond)\n\n\t\t\treturn b.delay(signature, delayMs)\n\t\t}\n\t}\n\n\tqueue := b.GetConfig().DefaultQueue\n\tbindingKey := b.GetConfig().AMQP.BindingKey // queue binding key\n\tif b.isDirectExchange() {\n\t\tqueue = signature.RoutingKey\n\t\tbindingKey = signature.RoutingKey\n\t}\n\n\tconnection, err := b.GetOrOpenConnection(\n\t\tqueue,\n\t\tbindingKey, // queue binding key\n\t\tnil,        // exchange declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueDeclareArgs), // queue declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueBindingArgs), // queue binding args\n\t)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"Failed to get a connection for queue %s\", queue)\n\t}\n\n\tchannel := connection.channel\n\tconfirmsChan := connection.confirmation\n\n\tif err := channel.Publish(\n\t\tb.GetConfig().AMQP.Exchange, // exchange name\n\t\tsignature.RoutingKey,        // routing key\n\t\tfalse,                       // mandatory\n\t\tfalse,                       // immediate\n\t\tamqp.Publishing{\n\t\t\tHeaders:      amqp.Table(signature.Headers),\n\t\t\tContentType:  \"application/json\",\n\t\t\tBody:         msg,\n\t\t\tPriority:     signature.Priority,\n\t\t\tDeliveryMode: amqp.Persistent,\n\t\t},\n\t); err != nil {\n\t\treturn errors.Wrap(err, \"Failed to publish task\")\n\t}\n\n\tconfirmed := <-confirmsChan\n\n\tif confirmed.Ack {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"Failed delivery of delivery tag: %v\", confirmed.DeliveryTag)\n}\n\n// consume takes delivered messages from the channel and manages a worker pool\n// to process tasks concurrently\nfunc (b *Broker) consume(deliveries <-chan amqp.Delivery, concurrency int, taskProcessor iface.TaskProcessor, amqpCloseChan <-chan *amqp.Error) error {\n\tpool := make(chan struct{}, concurrency)\n\n\t// initialize worker pool with maxWorkers workers\n\tgo func() {\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tpool <- struct{}{}\n\t\t}\n\t}()\n\n\t// make channel with a capacity makes it become a buffered channel so that a worker which wants to\n\t// push an error to `errorsChan` doesn't need to be blocked while the for-loop is blocked waiting\n\t// a worker, that is, it avoids a possible deadlock\n\terrorsChan := make(chan error, 1)\n\n\tfor {\n\t\tselect {\n\t\tcase amqpErr := <-amqpCloseChan:\n\t\t\treturn amqpErr\n\t\tcase err := <-errorsChan:\n\t\t\treturn err\n\t\tcase d := <-deliveries:\n\t\t\tif concurrency > 0 {\n\t\t\t\t// get worker from pool (blocks until one is available)\n\t\t\t\t<-pool\n\t\t\t}\n\n\t\t\tb.processingWG.Add(1)\n\n\t\t\t// Consume the task inside a gotourine so multiple tasks\n\t\t\t// can be processed concurrently\n\t\t\tgo func() {\n\t\t\t\tif err := b.consumeOne(d, taskProcessor, true); err != nil {\n\t\t\t\t\terrorsChan <- err\n\t\t\t\t}\n\n\t\t\t\tb.processingWG.Done()\n\n\t\t\t\tif concurrency > 0 {\n\t\t\t\t\t// give worker back to pool\n\t\t\t\t\tpool <- struct{}{}\n\t\t\t\t}\n\t\t\t}()\n\t\tcase <-b.GetStopChan():\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// consumeOne processes a single message using TaskProcessor\nfunc (b *Broker) consumeOne(delivery amqp.Delivery, taskProcessor iface.TaskProcessor, ack bool) error {\n\tif len(delivery.Body) == 0 {\n\t\tdelivery.Nack(true, false)                     // multiple, requeue\n\t\treturn errors.New(\"Received an empty message\") // RabbitMQ down?\n\t}\n\n\tvar multiple, requeue = false, false\n\n\t// Unmarshal message body into signature struct\n\tsignature := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewReader(delivery.Body))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(signature); err != nil {\n\t\tdelivery.Nack(multiple, requeue)\n\t\treturn errs.NewErrCouldNotUnmarshalTaskSignature(delivery.Body, err)\n\t}\n\n\t// If the task is not registered, we nack it and requeue,\n\t// there might be different workers for processing specific tasks\n\tif !b.IsTaskRegistered(signature.Name) {\n\t\tif !signature.IgnoreWhenTaskNotRegistered {\n\t\t\trequeue = true\n\t\t}\n\t\tlog.INFO.Printf(\"Task not registered with this worker. Requeuing: %t with message: %s\", requeue, delivery.Body)\n\t\tdelivery.Nack(multiple, requeue)\n\t\treturn nil\n\t}\n\n\tlog.DEBUG.Printf(\"Received new message: %s\", delivery.Body)\n\n\terr := taskProcessor.Process(signature)\n\tif ack {\n\t\tdelivery.Ack(multiple)\n\t}\n\treturn err\n}\n\n// delay a task by delayDuration miliseconds, the way it works is a new queue\n// is created without any consumers, the message is then published to this queue\n// with appropriate ttl expiration headers, after the expiration, it is sent to\n// the proper queue with consumers\nfunc (b *Broker) delay(signature *tasks.Signature, delayMs int64) error {\n\tif delayMs <= 0 {\n\t\treturn errors.New(\"Cannot delay task by 0ms\")\n\t}\n\n\tmessage, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tqueueName := b.GetConfig().AMQP.DelayedQueue\n\tdeclareQueueArgs := amqp.Table{\n\t\t// Exchange where to send messages after TTL expiration.\n\t\t\"x-dead-letter-exchange\": b.GetConfig().AMQP.Exchange,\n\t\t// Routing key which use when resending expired messages.\n\t\t\"x-dead-letter-routing-key\": signature.RoutingKey,\n\t}\n\tmessageProperties := amqp.Publishing{\n\t\tHeaders:      amqp.Table(signature.Headers),\n\t\tContentType:  \"application/json\",\n\t\tBody:         message,\n\t\tDeliveryMode: amqp.Persistent,\n\t\tExpiration:   fmt.Sprint(delayMs),\n\t}\n\tif queueName == \"\" {\n\t\t// It's necessary to redeclare the queue each time (to zero its TTL timer).\n\t\tqueueName = fmt.Sprintf(\n\t\t\t\"delay.%d.%s.%s\",\n\t\t\tdelayMs, // delay duration in mileseconds\n\t\t\tb.GetConfig().AMQP.Exchange,\n\t\t\tsignature.RoutingKey, // routing key\n\t\t)\n\t\tdeclareQueueArgs = amqp.Table{\n\t\t\t// Exchange where to send messages after TTL expiration.\n\t\t\t\"x-dead-letter-exchange\": b.GetConfig().AMQP.Exchange,\n\t\t\t// Routing key which use when resending expired messages.\n\t\t\t\"x-dead-letter-routing-key\": signature.RoutingKey,\n\t\t\t// Time in milliseconds\n\t\t\t// after that message will expire and be sent to destination.\n\t\t\t\"x-message-ttl\": delayMs,\n\t\t\t// Time after that the queue will be deleted.\n\t\t\t\"x-expires\": delayMs * 2,\n\t\t}\n\t\tmessageProperties = amqp.Publishing{\n\t\t\tHeaders:      amqp.Table(signature.Headers),\n\t\t\tContentType:  \"application/json\",\n\t\t\tBody:         message,\n\t\t\tDeliveryMode: amqp.Persistent,\n\t\t}\n\t}\n\n\tconn, channel, _, _, _, err := b.Connect(\n\t\tb.GetConfig().Broker,\n\t\tb.GetConfig().MultipleBrokerSeparator,\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\tqueueName,                       // queue name\n\t\ttrue,                            // queue durable\n\t\tb.GetConfig().AMQP.AutoDelete,   // queue delete when unused\n\t\tqueueName,                       // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tdeclareQueueArgs,                // queue declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueBindingArgs), // queue binding args\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer b.Close(channel, conn)\n\n\tif err := channel.Publish(\n\t\tb.GetConfig().AMQP.Exchange, // exchange\n\t\tqueueName,                   // routing key\n\t\tfalse,                       // mandatory\n\t\tfalse,                       // immediate\n\t\tmessageProperties,\n\t); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (b *Broker) isDirectExchange() bool {\n\treturn b.GetConfig().AMQP != nil && b.GetConfig().AMQP.ExchangeType == \"direct\"\n}\n\n// AdjustRoutingKey makes sure the routing key is correct.\n// If the routing key is an empty string:\n// a) set it to binding key for direct exchange type\n// b) set it to default queue name\nfunc (b *Broker) AdjustRoutingKey(s *tasks.Signature) {\n\tif s.RoutingKey != \"\" {\n\t\treturn\n\t}\n\n\tif b.isDirectExchange() {\n\t\t// The routing algorithm behind a direct exchange is simple - a message goes\n\t\t// to the queues whose binding key exactly matches the routing key of the message.\n\t\ts.RoutingKey = b.GetConfig().AMQP.BindingKey\n\t\treturn\n\t}\n\n\ts.RoutingKey = b.GetConfig().DefaultQueue\n}\n\n// Helper type for GetPendingTasks to accumulate signatures\ntype sigDumper struct {\n\tcustomQueue string\n\tSignatures  []*tasks.Signature\n}\n\nfunc (s *sigDumper) Process(sig *tasks.Signature) error {\n\ts.Signatures = append(s.Signatures, sig)\n\treturn nil\n}\n\nfunc (s *sigDumper) CustomQueue() string {\n\treturn s.customQueue\n}\n\nfunc (_ *sigDumper) PreConsumeHandler() bool {\n\treturn true\n}\n\nfunc (b *Broker) GetPendingTasks(queue string) ([]*tasks.Signature, error) {\n\tif queue == \"\" {\n\t\tqueue = b.GetConfig().DefaultQueue\n\t}\n\n\tbindingKey := b.GetConfig().AMQP.BindingKey // queue binding key\n\tconn, err := b.GetOrOpenConnection(\n\t\tqueue,\n\t\tbindingKey, // queue binding key\n\t\tnil,        // exchange declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueDeclareArgs), // queue declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueBindingArgs), // queue binding args\n\t)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"Failed to get a connection for queue %s\", queue)\n\t}\n\n\tchannel := conn.channel\n\tqueueInfo, err := channel.QueueInspect(queue)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"Failed to get info for queue %s\", queue)\n\t}\n\n\tvar tag uint64\n\tdefer channel.Nack(tag, true, true) // multiple, requeue\n\n\tdumper := &sigDumper{customQueue: queue}\n\tfor i := 0; i < queueInfo.Messages; i++ {\n\t\td, _, err := channel.Get(queue, false)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"Failed to get from queue\")\n\t\t}\n\t\ttag = d.DeliveryTag\n\t\tb.consumeOne(d, dumper, false)\n\t}\n\n\treturn dumper.Signatures, nil\n}\n"
  },
  {
    "path": "v1/brokers/amqp/amqp_concurrence_test.go",
    "content": "package amqp\n\nimport (\n\t\"fmt\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype doNothingProcessor struct{}\n\nfunc (_ doNothingProcessor) Process(signature *tasks.Signature) error {\n\treturn fmt.Errorf(\"failed\")\n}\n\nfunc (_ doNothingProcessor) CustomQueue() string {\n\treturn \"oops\"\n}\n\nfunc (_ doNothingProcessor) PreConsumeHandler() bool {\n\treturn true\n}\n\nfunc TestConsume(t *testing.T) {\n\tvar (\n\t\tiBroker    iface.Broker\n\t\tdeliveries = make(chan amqp.Delivery, 3)\n\t\tcloseChan  chan *amqp.Error\n\t\tprocessor  doNothingProcessor\n\t)\n\n\tt.Run(\"with deliveries more than the number of concurrency\", func(t *testing.T) {\n\t\tiBroker = New(&config.Config{})\n\t\tbroker, _ := iBroker.(*Broker)\n\t\terrChan := make(chan error)\n\n\t\t// simulate that there are too much deliveries\n\t\tgo func() {\n\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\tdeliveries <- amqp.Delivery{} // broker.consumeOne() will complain this error: Received an empty message\n\t\t\t}\n\t\t}()\n\n\t\tgo func() {\n\t\t\terr := broker.consume(deliveries, 2, processor, closeChan)\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}()\n\n\t\tselect {\n\t\tcase <-errChan:\n\t\tcase <-time.After(1 * time.Second):\n\t\t\tt.Error(\"Maybe deadlock\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "v1/brokers/amqp/amqp_test.go",
    "content": "package amqp_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/brokers/amqp\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestAdjustRoutingKey(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\ts      *tasks.Signature\n\t\tbroker iface.Broker\n\t)\n\n\tt.Run(\"with routing and binding keys\", func(t *testing.T) {\n\t\ts := &tasks.Signature{RoutingKey: \"routing_key\"}\n\t\tbroker = amqp.New(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t\tAMQP: &config.AMQPConfig{\n\t\t\t\tExchangeType: \"direct\",\n\t\t\t\tBindingKey:   \"binding_key\",\n\t\t\t},\n\t\t})\n\t\tbroker.AdjustRoutingKey(s)\n\t\tassert.Equal(t, \"routing_key\", s.RoutingKey)\n\t})\n\n\tt.Run(\"with binding key\", func(t *testing.T) {\n\t\ts = new(tasks.Signature)\n\t\tbroker = amqp.New(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t\tAMQP: &config.AMQPConfig{\n\t\t\t\tExchangeType: \"direct\",\n\t\t\t\tBindingKey:   \"binding_key\",\n\t\t\t},\n\t\t})\n\t\tbroker.AdjustRoutingKey(s)\n\t\tassert.Equal(t, \"binding_key\", s.RoutingKey)\n\t})\n}\n"
  },
  {
    "path": "v1/brokers/eager/eager.go",
    "content": "package eager\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// Broker represents an \"eager\" in-memory broker\ntype Broker struct {\n\tworker iface.TaskProcessor\n\tcommon.Broker\n}\n\n// New creates new Broker instance\nfunc New() iface.Broker {\n\treturn new(Broker)\n}\n\n// Mode interface with methods specific for this broker\ntype Mode interface {\n\tAssignWorker(p iface.TaskProcessor)\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (eagerBroker *Broker) StartConsuming(consumerTag string, concurrency int, p iface.TaskProcessor) (bool, error) {\n\treturn true, nil\n}\n\n// StopConsuming quits the loop\nfunc (eagerBroker *Broker) StopConsuming() {\n\t// do nothing\n}\n\n// Publish places a new message on the default queue\nfunc (eagerBroker *Broker) Publish(ctx context.Context, task *tasks.Signature) error {\n\tif eagerBroker.worker == nil {\n\t\treturn errors.New(\"worker is not assigned in eager-mode\")\n\t}\n\n\t// faking the behavior to marshal input into json\n\t// and unmarshal it back\n\tmessage, err := json.Marshal(task)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tsignature := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewReader(message))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(signature); err != nil {\n\t\treturn fmt.Errorf(\"JSON unmarshal error: %s\", err)\n\t}\n\n\t// blocking call to the task directly\n\treturn eagerBroker.worker.Process(signature)\n}\n\n// AssignWorker assigns a worker to the eager broker\nfunc (eagerBroker *Broker) AssignWorker(w iface.TaskProcessor) {\n\teagerBroker.worker = w\n}\n"
  },
  {
    "path": "v1/brokers/errs/errors.go",
    "content": "package errs\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// ErrCouldNotUnmarshalTaskSignature ...\ntype ErrCouldNotUnmarshalTaskSignature struct {\n\tmsg    []byte\n\treason string\n}\n\n// Error implements the error interface\nfunc (e ErrCouldNotUnmarshalTaskSignature) Error() string {\n\treturn fmt.Sprintf(\"Could not unmarshal '%s' into a task signature: %v\", e.msg, e.reason)\n}\n\n// NewErrCouldNotUnmarshalTaskSignature returns new ErrCouldNotUnmarshalTaskSignature instance\nfunc NewErrCouldNotUnmarshalTaskSignature(msg []byte, err error) ErrCouldNotUnmarshalTaskSignature {\n\treturn ErrCouldNotUnmarshalTaskSignature{msg: msg, reason: err.Error()}\n}\n\n// ErrConsumerStopped indicates that the operation is now illegal because of the consumer being stopped.\nvar ErrConsumerStopped = errors.New(\"the server has been stopped\")\n\n// ErrStopTaskDeletion indicates that the task should not be deleted from source after task failure\nvar ErrStopTaskDeletion = errors.New(\"task should not be deleted\")\n"
  },
  {
    "path": "v1/brokers/gcppubsub/gcp_pubsub.go",
    "content": "package gcppubsub\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"cloud.google.com/go/pubsub\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// Broker represents an Google Cloud Pub/Sub broker\ntype Broker struct {\n\tcommon.Broker\n\n\tservice          *pubsub.Client\n\tsubscriptionName string\n\tMaxExtension     time.Duration\n\n\tstopDone chan struct{}\n}\n\n// New creates new Broker instance\nfunc New(cnf *config.Config, projectID, subscriptionName string) (iface.Broker, error) {\n\tb := &Broker{Broker: common.NewBroker(cnf), stopDone: make(chan struct{})}\n\tb.subscriptionName = subscriptionName\n\n\tctx := context.Background()\n\n\tif cnf.GCPPubSub != nil {\n\t\tb.MaxExtension = cnf.GCPPubSub.MaxExtension\n\t}\n\n\tif cnf.GCPPubSub != nil && cnf.GCPPubSub.Client != nil {\n\t\tb.service = cnf.GCPPubSub.Client\n\t} else {\n\t\tpubsubClient, err := pubsub.NewClient(ctx, projectID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb.service = pubsubClient\n\t\tcnf.GCPPubSub = &config.GCPPubSubConfig{\n\t\t\tClient: pubsubClient,\n\t\t}\n\t}\n\n\t// Validate topic exists\n\tdefaultQueue := b.GetConfig().DefaultQueue\n\ttopic := b.service.Topic(defaultQueue)\n\tdefer topic.Stop()\n\n\ttopicExists, err := topic.Exists(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !topicExists {\n\t\treturn nil, fmt.Errorf(\"topic does not exist, instead got %s\", defaultQueue)\n\t}\n\n\t// Validate subscription exists\n\tsub := b.service.Subscription(b.subscriptionName)\n\n\tif b.MaxExtension != 0 {\n\t\tsub.ReceiveSettings.MaxExtension = b.MaxExtension\n\t}\n\n\tsubscriptionExists, err := sub.Exists(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !subscriptionExists {\n\t\treturn nil, fmt.Errorf(\"subscription does not exist, instead got %s\", b.subscriptionName)\n\t}\n\n\treturn b, nil\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\n\tsub := b.service.Subscription(b.subscriptionName)\n\n\tif b.MaxExtension != 0 {\n\t\tsub.ReceiveSettings.MaxExtension = b.MaxExtension\n\t}\n\n\tsub.ReceiveSettings.NumGoroutines = concurrency\n\tsub.ReceiveSettings.MaxOutstandingMessages = concurrency\n\tlog.INFO.Print(\"[*] Waiting for messages. To exit press CTRL+C\")\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func() {\n\t\t<-b.GetStopChan()\n\t\tcancel()\n\t}()\n\n\tfor {\n\t\terr := sub.Receive(ctx, func(_ctx context.Context, msg *pubsub.Message) {\n\t\t\tb.consumeOne(msg, taskProcessor)\n\t\t})\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tlog.ERROR.Printf(\"Error when receiving messages. Error: %v\", err)\n\t\tcontinue\n\t}\n\n\tclose(b.stopDone)\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *Broker) StopConsuming() {\n\tb.Broker.StopConsuming()\n\n\t// Waiting for any tasks being processed to finish\n\t<-b.stopDone\n}\n\n// Publish places a new message on the default queue or the queue pointed to\n// by the routing key\nfunc (b *Broker) Publish(ctx context.Context, signature *tasks.Signature) error {\n\t// Adjust routing key (this decides which queue the message will be published to)\n\tb.AdjustRoutingKey(signature)\n\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\ttopic := b.service.Topic(signature.RoutingKey)\n\tdefer topic.Stop()\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// delay the task\n\tif signature.ETA != nil {\n\t\tnow := time.Now().UTC()\n\n\t\tif signature.ETA.After(now) {\n\t\t\ttopic.PublishSettings.DelayThreshold = signature.ETA.Sub(now)\n\t\t}\n\t}\n\n\tresult := topic.Publish(ctx, &pubsub.Message{\n\t\tData: msg,\n\t})\n\n\tid, err := result.Get(ctx)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Error when sending a message: %v\", err)\n\t\treturn err\n\t}\n\n\tlog.INFO.Printf(\"Sending a message successfully, server-generated message ID %v\", id)\n\treturn nil\n}\n\n// consumeOne processes a single message using TaskProcessor\nfunc (b *Broker) consumeOne(delivery *pubsub.Message, taskProcessor iface.TaskProcessor) {\n\tif len(delivery.Data) == 0 {\n\t\tdelivery.Nack()\n\t\tlog.ERROR.Printf(\"received an empty message, the delivery was %v\", delivery)\n\t}\n\n\tsig := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewBuffer(delivery.Data))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(sig); err != nil {\n\t\tdelivery.Nack()\n\t\tlog.ERROR.Printf(\"unmarshal error. the delivery is %v\", delivery)\n\t}\n\n\t// If the task is not registered return an error\n\t// and leave the message in the queue\n\tif !b.IsTaskRegistered(sig.Name) {\n\t\tdelivery.Nack()\n\t\tlog.ERROR.Printf(\"task %s is not registered\", sig.Name)\n\t}\n\n\terr := taskProcessor.Process(sig)\n\tif err != nil {\n\t\tdelivery.Nack()\n\t\tlog.ERROR.Printf(\"Failed process of task\", err)\n\t}\n\n\t// Call Ack() after successfully consuming and processing the message\n\tdelivery.Ack()\n}\n"
  },
  {
    "path": "v1/brokers/iface/interfaces.go",
    "content": "package iface\n\nimport (\n\t\"context\"\n\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// Broker - a common interface for all brokers\ntype Broker interface {\n\tGetConfig() *config.Config\n\tSetRegisteredTaskNames(names []string)\n\tIsTaskRegistered(name string) bool\n\tStartConsuming(consumerTag string, concurrency int, p TaskProcessor) (bool, error)\n\tStopConsuming()\n\tPublish(ctx context.Context, task *tasks.Signature) error\n\tGetPendingTasks(queue string) ([]*tasks.Signature, error)\n\tGetDelayedTasks() ([]*tasks.Signature, error)\n\tAdjustRoutingKey(s *tasks.Signature)\n}\n\n// TaskProcessor - can process a delivered task\n// This will probably always be a worker instance\ntype TaskProcessor interface {\n\tProcess(signature *tasks.Signature) error\n\tCustomQueue() string\n\tPreConsumeHandler() bool\n}\n"
  },
  {
    "path": "v1/brokers/package.go",
    "content": "package brokers\n"
  },
  {
    "path": "v1/brokers/redis/goredis.go",
    "content": "package redis\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-redsync/redsync/v4\"\n\t\"github.com/redis/go-redis/v9\"\n\n\t\"github.com/RichardKnop/machinery/v1/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\n// BrokerGR represents a Redis broker\ntype BrokerGR struct {\n\tcommon.Broker\n\trclient      redis.UniversalClient\n\tconsumingWG  sync.WaitGroup // wait group to make sure whole consumption completes\n\tprocessingWG sync.WaitGroup // use wait group to make sure task processing completes\n\tdelayedWG    sync.WaitGroup\n\t// If set, path to a socket file overrides hostname\n\tsocketPath           string\n\tredsync              *redsync.Redsync\n\tredisOnce            sync.Once\n\tredisDelayedTasksKey string\n}\n\n// NewGR creates new Broker instance\nfunc NewGR(cnf *config.Config, addrs []string, db int) iface.Broker {\n\tb := &BrokerGR{Broker: common.NewBroker(cnf)}\n\n\tvar password string\n\tvar username string\n\tparts := strings.Split(addrs[0], \"@\")\n\tif len(parts) >= 2 {\n\t\t// with password\n\t\toptions := strings.SplitN(strings.Join(parts[:len(parts)-1], \"@\"), \":\", 2)\n\t\tif len(options) >= 2 {\n\t\t\tusername = options[0]\n\t\t\tpassword = options[1]\n\t\t} else {\n\t\t\tpassword = options[0]\n\t\t}\n\n\t\taddrs[0] = parts[len(parts)-1] // addr is the last one without @\n\t}\n\n\tropt := &redis.UniversalOptions{\n\t\tAddrs:    addrs,\n\t\tDB:       db,\n\t\tPassword: password,\n\t\tUsername: username,\n\t}\n\tif cnf.Redis != nil {\n\t\tropt.MasterName = cnf.Redis.MasterName\n\t}\n\n\tif cnf.Redis != nil && cnf.Redis.ClusterMode {\n\t\tb.rclient = redis.NewClusterClient(ropt.Cluster())\n\t} else {\n\t\tb.rclient = redis.NewUniversalClient(ropt)\n\t}\n\n\tif cnf.Redis.DelayedTasksKey != \"\" {\n\t\tb.redisDelayedTasksKey = cnf.Redis.DelayedTasksKey\n\t} else {\n\t\tb.redisDelayedTasksKey = defaultRedisDelayedTasksKey\n\t}\n\treturn b\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *BrokerGR) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.consumingWG.Add(1)\n\tdefer b.consumingWG.Done()\n\n\tif concurrency < 1 {\n\t\tconcurrency = runtime.NumCPU() * 2\n\t}\n\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\n\t// Ping the server to make sure connection is live\n\t_, err := b.rclient.Ping(context.Background()).Result()\n\tif err != nil {\n\t\tb.GetRetryFunc()(b.GetRetryStopChan())\n\n\t\t// Return err if retry is still true.\n\t\t// If retry is false, broker.StopConsuming() has been called and\n\t\t// therefore Redis might have been stopped. Return nil exit\n\t\t// StartConsuming()\n\t\tif b.GetRetry() {\n\t\t\treturn b.GetRetry(), err\n\t\t}\n\t\treturn b.GetRetry(), errs.ErrConsumerStopped\n\t}\n\n\t// Channel to which we will push tasks ready for processing by worker\n\tdeliveries := make(chan []byte, concurrency)\n\tpool := make(chan struct{}, concurrency)\n\n\t// initialize worker pool with maxWorkers workers\n\tfor i := 0; i < concurrency; i++ {\n\t\tpool <- struct{}{}\n\t}\n\n\t// A receiving goroutine keeps popping messages from the queue by BLPOP\n\t// If the message is valid and can be unmarshaled into a proper structure\n\t// we send it to the deliveries channel\n\tgo func() {\n\n\t\tlog.INFO.Print(\"[*] Waiting for messages. To exit press CTRL+C\")\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.GetStopChan():\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tcase <-pool:\n\t\t\t\ttask, _ := b.nextTask(getQueueGR(b.GetConfig(), taskProcessor))\n\t\t\t\t//TODO: should this error be ignored?\n\t\t\t\tif len(task) > 0 {\n\t\t\t\t\tdeliveries <- task\n\t\t\t\t}\n\n\t\t\t\tpool <- struct{}{}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// A goroutine to watch for delayed tasks and push them to deliveries\n\t// channel for consumption by the worker\n\tb.delayedWG.Add(1)\n\tgo func() {\n\t\tdefer b.delayedWG.Done()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.GetStopChan():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\ttask, err := b.nextDelayedTask(b.redisDelayedTasksKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tsignature := new(tasks.Signature)\n\t\t\t\tdecoder := json.NewDecoder(bytes.NewReader(task))\n\t\t\t\tdecoder.UseNumber()\n\t\t\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\t\t\tlog.ERROR.Print(errs.NewErrCouldNotUnmarshalTaskSignature(task, err))\n\t\t\t\t}\n\n\t\t\t\tif err := b.Publish(context.Background(), signature); err != nil {\n\t\t\t\t\tlog.ERROR.Print(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tif err := b.consume(deliveries, concurrency, taskProcessor); err != nil {\n\t\treturn b.GetRetry(), err\n\t}\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *BrokerGR) StopConsuming() {\n\tb.Broker.StopConsuming()\n\t// Waiting for the delayed tasks goroutine to have stopped\n\tb.delayedWG.Wait()\n\t// Waiting for consumption to finish\n\tb.consumingWG.Wait()\n\n\tb.rclient.Close()\n}\n\n// Publish places a new message on the default queue\nfunc (b *BrokerGR) Publish(ctx context.Context, signature *tasks.Signature) error {\n\t// Adjust routing key (this decides which queue the message will be published to)\n\tb.Broker.AdjustRoutingKey(signature)\n\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// delay the task\n\tif signature.ETA != nil {\n\t\tnow := time.Now().UTC()\n\n\t\tif signature.ETA.After(now) {\n\t\t\tscore := signature.ETA.UnixNano()\n\t\t\terr = b.rclient.ZAdd(context.Background(), b.redisDelayedTasksKey, redis.Z{Score: float64(score), Member: msg}).Err()\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = b.rclient.RPush(context.Background(), signature.RoutingKey, msg).Err()\n\treturn err\n}\n\n// GetPendingTasks returns a slice of task signatures waiting in the queue\nfunc (b *BrokerGR) GetPendingTasks(queue string) ([]*tasks.Signature, error) {\n\n\tif queue == \"\" {\n\t\tqueue = b.GetConfig().DefaultQueue\n\t}\n\tresults, err := b.rclient.LRange(context.Background(), queue, 0, -1).Result()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttaskSignatures := make([]*tasks.Signature, len(results))\n\tfor i, result := range results {\n\t\tsignature := new(tasks.Signature)\n\t\tdecoder := json.NewDecoder(strings.NewReader(result))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskSignatures[i] = signature\n\t}\n\treturn taskSignatures, nil\n}\n\n// GetDelayedTasks returns a slice of task signatures that are scheduled, but not yet in the queue\nfunc (b *BrokerGR) GetDelayedTasks() ([]*tasks.Signature, error) {\n\tresults, err := b.rclient.ZRange(context.Background(), b.redisDelayedTasksKey, 0, -1).Result()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttaskSignatures := make([]*tasks.Signature, len(results))\n\tfor i, result := range results {\n\t\tsignature := new(tasks.Signature)\n\t\tdecoder := json.NewDecoder(strings.NewReader(result))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskSignatures[i] = signature\n\t}\n\treturn taskSignatures, nil\n}\n\n// consume takes delivered messages from the channel and manages a worker pool\n// to process tasks concurrently\nfunc (b *BrokerGR) consume(deliveries <-chan []byte, concurrency int, taskProcessor iface.TaskProcessor) error {\n\terrorsChan := make(chan error, concurrency*2)\n\tpool := make(chan struct{}, concurrency)\n\n\t// init pool for Worker tasks execution, as many slots as Worker concurrency param\n\tgo func() {\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tpool <- struct{}{}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase err := <-errorsChan:\n\t\t\treturn err\n\t\tcase d, open := <-deliveries:\n\t\t\tif !open {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif concurrency > 0 {\n\t\t\t\t// get execution slot from pool (blocks until one is available)\n\t\t\t\t<-pool\n\t\t\t}\n\n\t\t\tb.processingWG.Add(1)\n\n\t\t\t// Consume the task inside a goroutine so multiple tasks\n\t\t\t// can be processed concurrently\n\t\t\tgo func() {\n\t\t\t\tif err := b.consumeOne(d, taskProcessor); err != nil {\n\t\t\t\t\terrorsChan <- err\n\t\t\t\t}\n\n\t\t\t\tb.processingWG.Done()\n\n\t\t\t\tif concurrency > 0 {\n\t\t\t\t\t// give slot back to pool\n\t\t\t\t\tpool <- struct{}{}\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n}\n\n// consumeOne processes a single message using TaskProcessor\nfunc (b *BrokerGR) consumeOne(delivery []byte, taskProcessor iface.TaskProcessor) error {\n\tsignature := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewReader(delivery))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(signature); err != nil {\n\t\treturn errs.NewErrCouldNotUnmarshalTaskSignature(delivery, err)\n\t}\n\n\t// If the task is not registered, we requeue it,\n\t// there might be different workers for processing specific tasks\n\tif !b.IsTaskRegistered(signature.Name) {\n\t\tif signature.IgnoreWhenTaskNotRegistered {\n\t\t\treturn nil\n\t\t}\n\t\tlog.INFO.Printf(\"Task not registered with this worker. Requeuing message: %s\", delivery)\n\n\t\tb.rclient.RPush(context.Background(), getQueueGR(b.GetConfig(), taskProcessor), delivery)\n\t\treturn nil\n\t}\n\n\tlog.DEBUG.Printf(\"Received new message: %s\", delivery)\n\n\treturn taskProcessor.Process(signature)\n}\n\n// nextTask pops next available task from the default queue\nfunc (b *BrokerGR) nextTask(queue string) (result []byte, err error) {\n\n\tpollPeriodMilliseconds := 1000 // default poll period for normal tasks\n\tif b.GetConfig().Redis != nil {\n\t\tconfiguredPollPeriod := b.GetConfig().Redis.NormalTasksPollPeriod\n\t\tif configuredPollPeriod > 0 {\n\t\t\tpollPeriodMilliseconds = configuredPollPeriod\n\t\t}\n\t}\n\tpollPeriod := time.Duration(pollPeriodMilliseconds) * time.Millisecond\n\n\titems, err := b.rclient.BLPop(context.Background(), pollPeriod, queue).Result()\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\n\t// items[0] - the name of the key where an element was popped\n\t// items[1] - the value of the popped element\n\tif len(items) != 2 {\n\t\treturn []byte{}, redis.Nil\n\t}\n\n\tresult = []byte(items[1])\n\n\treturn result, nil\n}\n\n// nextDelayedTask pops a value from the ZSET key using WATCH/MULTI/EXEC commands.\nfunc (b *BrokerGR) nextDelayedTask(key string) (result []byte, err error) {\n\n\t//pipe := b.rclient.Pipeline()\n\t//\n\t//defer func() {\n\t//\t// Return connection to normal state on error.\n\t//\t// https://redis.io/commands/discard\n\t//\tif err != nil {\n\t//\t\tpipe.Discard()\n\t//\t}\n\t//}()\n\n\tvar (\n\t\titems []string\n\t)\n\n\tpollPeriod := 500 // default poll period for delayed tasks\n\tif b.GetConfig().Redis != nil {\n\t\tconfiguredPollPeriod := b.GetConfig().Redis.DelayedTasksPollPeriod\n\t\t// the default period is 0, which bombards redis with requests, despite\n\t\t// our intention of doing the opposite\n\t\tif configuredPollPeriod > 0 {\n\t\t\tpollPeriod = configuredPollPeriod\n\t\t}\n\t}\n\n\tfor {\n\t\t// Space out queries to ZSET so we don't bombard redis\n\t\t// server with relentless ZRANGEBYSCOREs\n\t\ttime.Sleep(time.Duration(pollPeriod) * time.Millisecond)\n\t\twatchFunc := func(tx *redis.Tx) error {\n\n\t\t\tnow := time.Now().UTC().UnixNano()\n\n\t\t\t// https://redis.io/commands/zrangebyscore\n\t\t\tctx := context.Background()\n\t\t\titems, err = tx.ZRevRangeByScore(ctx, key, &redis.ZRangeBy{\n\t\t\t\tMin: \"0\", Max: strconv.FormatInt(now, 10), Offset: 0, Count: 1,\n\t\t\t}).Result()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif len(items) != 1 {\n\t\t\t\treturn redis.Nil\n\t\t\t}\n\n\t\t\t// only return the first zrange value if there are no other changes in this key\n\t\t\t// to make sure a delayed task would only be consumed once\n\t\t\t_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {\n\t\t\t\tpipe.ZRem(ctx, key, items[0])\n\t\t\t\tresult = []byte(items[0])\n\t\t\t\treturn nil\n\t\t\t})\n\n\t\t\treturn err\n\t\t}\n\n\t\tif err = b.rclient.Watch(context.Background(), watchFunc, key); err != nil {\n\t\t\treturn\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc getQueueGR(config *config.Config, taskProcessor iface.TaskProcessor) string {\n\tcustomQueue := taskProcessor.CustomQueue()\n\tif customQueue == \"\" {\n\t\treturn config.DefaultQueue\n\t}\n\treturn customQueue\n}\n"
  },
  {
    "path": "v1/brokers/redis/redis.go",
    "content": "package redis\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-redsync/redsync/v4\"\n\tredsyncredis \"github.com/go-redsync/redsync/v4/redis/redigo\"\n\t\"github.com/gomodule/redigo/redis\"\n\n\t\"github.com/RichardKnop/machinery/v1/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\nconst defaultRedisDelayedTasksKey = \"delayed_tasks\"\n\n// Broker represents a Redis broker\ntype Broker struct {\n\tcommon.Broker\n\tcommon.RedisConnector\n\tusername     string\n\thost         string\n\tpassword     string\n\tdb           int\n\tpool         *redis.Pool\n\tconsumingWG  sync.WaitGroup // wait group to make sure whole consumption completes\n\tprocessingWG sync.WaitGroup // use wait group to make sure task processing completes\n\tdelayedWG    sync.WaitGroup\n\t// If set, path to a socket file overrides hostname\n\tsocketPath           string\n\tredsync              *redsync.Redsync\n\tredisOnce            sync.Once\n\tredisDelayedTasksKey string\n}\n\n// New creates new Broker instance\nfunc New(cnf *config.Config, host, username, password, socketPath string, db int) iface.Broker {\n\tb := &Broker{Broker: common.NewBroker(cnf)}\n\tb.host = host\n\tb.db = db\n\tb.username = username\n\tb.password = password\n\tb.socketPath = socketPath\n\n\tif cnf.Redis != nil && cnf.Redis.DelayedTasksKey != \"\" {\n\t\tb.redisDelayedTasksKey = cnf.Redis.DelayedTasksKey\n\t} else {\n\t\tb.redisDelayedTasksKey = defaultRedisDelayedTasksKey\n\t}\n\n\treturn b\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.consumingWG.Add(1)\n\tdefer b.consumingWG.Done()\n\n\tif concurrency < 1 {\n\t\tconcurrency = runtime.NumCPU() * 2\n\t}\n\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\n\tconn := b.open()\n\tdefer conn.Close()\n\n\t// Ping the server to make sure connection is live\n\t_, err := conn.Do(\"PING\")\n\tif err != nil {\n\t\tb.GetRetryFunc()(b.GetRetryStopChan())\n\n\t\t// Return err if retry is still true.\n\t\t// If retry is false, broker.StopConsuming() has been called and\n\t\t// therefore Redis might have been stopped. Return nil exit\n\t\t// StartConsuming()\n\t\tif b.GetRetry() {\n\t\t\treturn b.GetRetry(), err\n\t\t}\n\t\treturn b.GetRetry(), errs.ErrConsumerStopped\n\t}\n\n\t// Channel to which we will push tasks ready for processing by worker\n\tdeliveries := make(chan []byte, concurrency)\n\tpool := make(chan struct{}, concurrency)\n\tstopConsumer := make(chan struct{})\n\n\t// initialize worker pool with maxWorkers workers\n\tfor i := 0; i < concurrency; i++ {\n\t\tpool <- struct{}{}\n\t}\n\n\t// A receiving goroutine keeps popping messages from the queue by BLPOP\n\t// If the message is valid and can be unmarshaled into a proper structure\n\t// we send it to the deliveries channel\n\tgo func() {\n\n\t\tlog.INFO.Print(\"[*] Waiting for messages. To exit press CTRL+C\")\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.GetStopChan():\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tcase <-stopConsumer:\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tcase <-pool:\n\t\t\t\tselect {\n\t\t\t\tcase <-b.GetStopChan():\n\t\t\t\t\tclose(deliveries)\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t}\n\n\t\t\t\tif taskProcessor.PreConsumeHandler() {\n\t\t\t\t\ttask, _ := b.nextTask(getQueue(b.GetConfig(), taskProcessor))\n\t\t\t\t\t//TODO: should this error be ignored?\n\t\t\t\t\tif len(task) > 0 {\n\t\t\t\t\t\tdeliveries <- task\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tpool <- struct{}{}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// A goroutine to watch for delayed tasks and push them to deliveries\n\t// channel for consumption by the worker\n\tb.delayedWG.Add(1)\n\tgo func() {\n\t\tdefer b.delayedWG.Done()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.GetStopChan():\n\t\t\t\treturn\n\t\t\tcase <-stopConsumer:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\ttask, err := b.nextDelayedTask(b.redisDelayedTasksKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tsignature := new(tasks.Signature)\n\t\t\t\tdecoder := json.NewDecoder(bytes.NewReader(task))\n\t\t\t\tdecoder.UseNumber()\n\t\t\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\t\t\tlog.ERROR.Print(errs.NewErrCouldNotUnmarshalTaskSignature(task, err))\n\t\t\t\t}\n\n\t\t\t\tif err := b.Publish(context.Background(), signature); err != nil {\n\t\t\t\t\tlog.ERROR.Print(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tif err := b.consume(deliveries, concurrency, taskProcessor, stopConsumer); err != nil {\n\t\treturn b.GetRetry(), err\n\t}\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *Broker) StopConsuming() {\n\tb.Broker.StopConsuming()\n\t// Waiting for the delayed tasks goroutine to have stopped\n\tb.delayedWG.Wait()\n\t// Waiting for consumption to finish\n\tb.consumingWG.Wait()\n\t// Wait for currently processing tasks to finish as well.\n\tb.processingWG.Wait()\n\n\tif b.pool != nil {\n\t\tb.pool.Close()\n\t}\n}\n\n// Publish places a new message on the default queue\nfunc (b *Broker) Publish(ctx context.Context, signature *tasks.Signature) error {\n\t// Adjust routing key (this decides which queue the message will be published to)\n\tb.Broker.AdjustRoutingKey(signature)\n\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tconn := b.open()\n\tdefer conn.Close()\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// delay the task\n\tif signature.ETA != nil {\n\t\tnow := time.Now().UTC()\n\n\t\tif signature.ETA.After(now) {\n\t\t\tscore := signature.ETA.UnixNano()\n\t\t\t_, err = conn.Do(\"ZADD\", b.redisDelayedTasksKey, score, msg)\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = conn.Do(\"RPUSH\", signature.RoutingKey, msg)\n\treturn err\n}\n\n// GetPendingTasks returns a slice of task signatures waiting in the queue\nfunc (b *Broker) GetPendingTasks(queue string) ([]*tasks.Signature, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tif queue == \"\" {\n\t\tqueue = b.GetConfig().DefaultQueue\n\t}\n\tdataBytes, err := conn.Do(\"LRANGE\", queue, 0, -1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults, err := redis.ByteSlices(dataBytes, err)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttaskSignatures := make([]*tasks.Signature, len(results))\n\tfor i, result := range results {\n\t\tsignature := new(tasks.Signature)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(result))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskSignatures[i] = signature\n\t}\n\treturn taskSignatures, nil\n}\n\n// GetDelayedTasks returns a slice of task signatures that are scheduled, but not yet in the queue\nfunc (b *Broker) GetDelayedTasks() ([]*tasks.Signature, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tdataBytes, err := conn.Do(\"ZRANGE\", b.redisDelayedTasksKey, 0, -1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults, err := redis.ByteSlices(dataBytes, err)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttaskSignatures := make([]*tasks.Signature, len(results))\n\tfor i, result := range results {\n\t\tsignature := new(tasks.Signature)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(result))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskSignatures[i] = signature\n\t}\n\treturn taskSignatures, nil\n}\n\n// consume takes delivered messages from the channel and manages a worker pool\n// to process tasks concurrently\nfunc (b *Broker) consume(deliveries <-chan []byte, concurrency int, taskProcessor iface.TaskProcessor, stopConsumer chan struct{}) error {\n\terrorsChan := make(chan error, concurrency*2)\n\tpool := make(chan struct{}, concurrency)\n\n\t// init pool for Worker tasks execution, as many slots as Worker concurrency param\n\tgo func() {\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tpool <- struct{}{}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase err := <-errorsChan:\n\t\t\tclose(stopConsumer)\n\t\t\tfor v := range deliveries {\n\t\t\t\tb.requeueMessage(v, taskProcessor)\n\t\t\t}\n\t\t\treturn err\n\t\tcase d, open := <-deliveries:\n\t\t\tif !open {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif concurrency > 0 {\n\t\t\t\t// get execution slot from pool (blocks until one is available)\n\t\t\t\tselect {\n\t\t\t\tcase <-b.GetStopChan():\n\t\t\t\t\tb.requeueMessage(d, taskProcessor)\n\t\t\t\t\tcontinue\n\t\t\t\tcase <-pool:\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tb.processingWG.Add(1)\n\n\t\t\t// Consume the task inside a goroutine so multiple tasks\n\t\t\t// can be processed concurrently\n\t\t\tgo func() {\n\t\t\t\tif err := b.consumeOne(d, taskProcessor); err != nil {\n\t\t\t\t\terrorsChan <- err\n\t\t\t\t}\n\n\t\t\t\tb.processingWG.Done()\n\n\t\t\t\tif concurrency > 0 {\n\t\t\t\t\t// give slot back to pool\n\t\t\t\t\tpool <- struct{}{}\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n}\n\n// consumeOne processes a single message using TaskProcessor\nfunc (b *Broker) consumeOne(delivery []byte, taskProcessor iface.TaskProcessor) error {\n\tsignature := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewReader(delivery))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(signature); err != nil {\n\t\treturn errs.NewErrCouldNotUnmarshalTaskSignature(delivery, err)\n\t}\n\n\t// If the task is not registered, we requeue it,\n\t// there might be different workers for processing specific tasks\n\tif !b.IsTaskRegistered(signature.Name) {\n\t\tif signature.IgnoreWhenTaskNotRegistered {\n\t\t\treturn nil\n\t\t}\n\t\tlog.INFO.Printf(\"Task not registered with this worker. Requeuing message: %s\", delivery)\n\t\tb.requeueMessage(delivery, taskProcessor)\n\t\treturn nil\n\t}\n\n\tlog.DEBUG.Printf(\"Received new message: %s\", delivery)\n\n\treturn taskProcessor.Process(signature)\n}\n\n// nextTask pops next available task from the default queue\nfunc (b *Broker) nextTask(queue string) (result []byte, err error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tpollPeriodMilliseconds := 1000 // default poll period for normal tasks\n\tif b.GetConfig().Redis != nil {\n\t\tconfiguredPollPeriod := b.GetConfig().Redis.NormalTasksPollPeriod\n\t\tif configuredPollPeriod > 0 {\n\t\t\tpollPeriodMilliseconds = configuredPollPeriod\n\t\t}\n\t}\n\tpollPeriod := time.Duration(pollPeriodMilliseconds) * time.Millisecond\n\n\t// Issue 548: BLPOP expects an integer timeout expresses in seconds.\n\t// The call will if the value is a float. Convert to integer using\n\t// math.Ceil():\n\t//   math.Ceil(0.0) --> 0 (block indefinitely)\n\t//   math.Ceil(0.2) --> 1 (timeout after 1 second)\n\tpollPeriodSeconds := math.Ceil(pollPeriod.Seconds())\n\n\titems, err := redis.ByteSlices(conn.Do(\"BLPOP\", queue, pollPeriodSeconds))\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\n\t// items[0] - the name of the key where an element was popped\n\t// items[1] - the value of the popped element\n\tif len(items) != 2 {\n\t\treturn []byte{}, redis.ErrNil\n\t}\n\n\tresult = items[1]\n\n\treturn result, nil\n}\n\n// nextDelayedTask pops a value from the ZSET key using WATCH/MULTI/EXEC commands.\n// https://github.com/gomodule/redigo/blob/master/redis/zpop_example_test.go\nfunc (b *Broker) nextDelayedTask(key string) (result []byte, err error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tdefer func() {\n\t\t// Return connection to normal state on error.\n\t\t// https://redis.io/commands/discard\n\t\t// https://redis.io/commands/unwatch\n\t\tif err == redis.ErrNil {\n\t\t\tconn.Do(\"UNWATCH\")\n\t\t} else if err != nil {\n\t\t\tconn.Do(\"DISCARD\")\n\t\t}\n\t}()\n\n\tvar (\n\t\titems [][]byte\n\t\treply interface{}\n\t)\n\n\tpollPeriod := 500 // default poll period for delayed tasks\n\tif b.GetConfig().Redis != nil {\n\t\tconfiguredPollPeriod := b.GetConfig().Redis.DelayedTasksPollPeriod\n\t\t// the default period is 0, which bombards redis with requests, despite\n\t\t// our intention of doing the opposite\n\t\tif configuredPollPeriod > 0 {\n\t\t\tpollPeriod = configuredPollPeriod\n\t\t}\n\t}\n\n\tfor {\n\t\t// Space out queries to ZSET so we don't bombard redis\n\t\t// server with relentless ZRANGEBYSCOREs\n\t\ttime.Sleep(time.Duration(pollPeriod) * time.Millisecond)\n\t\tif _, err = conn.Do(\"WATCH\", key); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tnow := time.Now().UTC().UnixNano()\n\n\t\t// https://redis.io/commands/zrangebyscore\n\t\titems, err = redis.ByteSlices(conn.Do(\n\t\t\t\"ZRANGEBYSCORE\",\n\t\t\tkey,\n\t\t\t0,\n\t\t\tnow,\n\t\t\t\"LIMIT\",\n\t\t\t0,\n\t\t\t1,\n\t\t))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif len(items) != 1 {\n\t\t\terr = redis.ErrNil\n\t\t\treturn\n\t\t}\n\n\t\t_ = conn.Send(\"MULTI\")\n\t\t_ = conn.Send(\"ZREM\", key, items[0])\n\t\treply, err = conn.Do(\"EXEC\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif reply != nil {\n\t\t\tresult = items[0]\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\n// open returns or creates instance of Redis connection\nfunc (b *Broker) open() redis.Conn {\n\tb.redisOnce.Do(func() {\n\t\tb.pool = b.NewPool(b.socketPath, b.host, b.username, b.password, b.db, b.GetConfig().Redis, b.GetConfig().TLSConfig)\n\t\tb.redsync = redsync.New(redsyncredis.NewPool(b.pool))\n\t})\n\n\treturn b.pool.Get()\n}\n\nfunc getQueue(config *config.Config, taskProcessor iface.TaskProcessor) string {\n\tcustomQueue := taskProcessor.CustomQueue()\n\tif customQueue == \"\" {\n\t\treturn config.DefaultQueue\n\t}\n\treturn customQueue\n}\n\nfunc (b *Broker) requeueMessage(delivery []byte, taskProcessor iface.TaskProcessor) {\n\tconn := b.open()\n\tdefer conn.Close()\n\tconn.Do(\"RPUSH\", getQueue(b.GetConfig(), taskProcessor), delivery)\n}\n"
  },
  {
    "path": "v1/brokers/sqs/sqs.go",
    "content": "package sqs\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/sqs/sqsiface\"\n\n\tawssqs \"github.com/aws/aws-sdk-go/service/sqs\"\n)\n\nconst (\n\tmaxAWSSQSDelay = time.Minute * 15 // Max supported SQS delay is 15 min: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html\n)\n\n// Broker represents a AWS SQS broker\n// There are examples on: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/sqs-example-create-queue.html\ntype Broker struct {\n\tcommon.Broker\n\tprocessingWG      sync.WaitGroup // use wait group to make sure task processing completes on interrupt signal\n\treceivingWG       sync.WaitGroup\n\tstopReceivingChan chan int\n\tsess              *session.Session\n\tservice           sqsiface.SQSAPI\n\tqueueUrl          *string\n}\n\n// New creates new Broker instance\nfunc New(cnf *config.Config) iface.Broker {\n\tb := &Broker{Broker: common.NewBroker(cnf)}\n\tif cnf.SQS != nil && cnf.SQS.Client != nil {\n\t\t// Use provided *SQS client\n\t\tb.service = cnf.SQS.Client\n\t} else {\n\t\t// Initialize a session that the SDK will use to load credentials from the shared credentials file, ~/.aws/credentials.\n\t\t// See details on: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html\n\t\t// Also, env AWS_REGION is also required\n\t\tb.sess = session.Must(session.NewSessionWithOptions(session.Options{\n\t\t\tSharedConfigState: session.SharedConfigEnable,\n\t\t}))\n\t\tb.service = awssqs.New(b.sess)\n\t}\n\n\treturn b\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\tqURL := b.getQueueURL(taskProcessor)\n\t//save it so that it can be used later when attempting to delete task\n\tb.queueUrl = qURL\n\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput, concurrency)\n\tpool := make(chan struct{}, concurrency)\n\n\t// initialize worker pool with maxWorkers workers\n\tfor i := 0; i < concurrency; i++ {\n\t\tpool <- struct{}{}\n\t}\n\tb.stopReceivingChan = make(chan int)\n\tb.receivingWG.Add(1)\n\n\tgo func() {\n\t\tdefer b.receivingWG.Done()\n\n\t\tlog.INFO.Printf(\"[*] Waiting for messages on queue: %s. To exit press CTRL+C\\n\", *qURL)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.stopReceivingChan:\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tcase <-pool:\n\t\t\t\toutput, err := b.receiveMessage(qURL)\n\t\t\t\tif err == nil && len(output.Messages) > 0 {\n\t\t\t\t\tdeliveries <- output\n\n\t\t\t\t} else {\n\t\t\t\t\t//return back to pool right away\n\t\t\t\t\tpool <- struct{}{}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.ERROR.Printf(\"Queue consume error: %s\", err)\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}()\n\n\tif err := b.consume(deliveries, concurrency, taskProcessor, pool); err != nil {\n\t\treturn b.GetRetry(), err\n\t}\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *Broker) StopConsuming() {\n\tb.Broker.StopConsuming()\n\n\tb.stopReceiving()\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n\n\t// Waiting for the receiving goroutine to have stopped\n\tb.receivingWG.Wait()\n}\n\n// Publish places a new message on the default queue\nfunc (b *Broker) Publish(ctx context.Context, signature *tasks.Signature) error {\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\t// Check that signature.RoutingKey is set, if not switch to DefaultQueue\n\tb.AdjustRoutingKey(signature)\n\n\tMsgInput := &awssqs.SendMessageInput{\n\t\tMessageBody: aws.String(string(msg)),\n\t\tQueueUrl:    aws.String(b.GetConfig().Broker + \"/\" + signature.RoutingKey),\n\t}\n\n\t// if this is a fifo queue, there needs to be some additional parameters.\n\tif strings.HasSuffix(signature.RoutingKey, \".fifo\") {\n\t\t// Use Machinery's signature Task UUID as SQS Message Group ID.\n\t\tMsgDedupID := signature.UUID\n\t\tMsgInput.MessageDeduplicationId = aws.String(MsgDedupID)\n\n\t\t// Do not Use Machinery's signature Group UUID as SQS Message Group ID, instead use BrokerMessageGroupId\n\t\tMsgGroupID := signature.BrokerMessageGroupId\n\t\tif MsgGroupID == \"\" {\n\t\t\treturn fmt.Errorf(\"please specify BrokerMessageGroupId attribute for task Signature when submitting a task to FIFO queue\")\n\t\t}\n\t\tMsgInput.MessageGroupId = aws.String(MsgGroupID)\n\t}\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// and is not a fifo queue, set a delay in seconds for the task.\n\tif signature.ETA != nil && !strings.HasSuffix(signature.RoutingKey, \".fifo\") {\n\t\tnow := time.Now().UTC()\n\t\tdelay := signature.ETA.Sub(now)\n\t\tif delay > 0 {\n\t\t\tif delay > maxAWSSQSDelay {\n\t\t\t\treturn errors.New(\"Max AWS SQS delay exceeded\")\n\t\t\t}\n\t\t\tMsgInput.DelaySeconds = aws.Int64(int64(delay.Seconds()))\n\t\t}\n\t}\n\n\tresult, err := b.service.SendMessageWithContext(ctx, MsgInput)\n\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Error when sending a message: %v\", err)\n\t\treturn err\n\n\t}\n\tlog.INFO.Printf(\"Sending a message successfully, the messageId is %v\", *result.MessageId)\n\treturn nil\n\n}\n\n// consume is a method which keeps consuming deliveries from a channel, until there is an error or a stop signal\nfunc (b *Broker) consume(deliveries <-chan *awssqs.ReceiveMessageOutput, concurrency int, taskProcessor iface.TaskProcessor, pool chan struct{}) error {\n\n\terrorsChan := make(chan error)\n\n\tfor {\n\t\twhetherContinue, err := b.consumeDeliveries(deliveries, concurrency, taskProcessor, pool, errorsChan)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif whetherContinue == false {\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// consumeOne is a method consumes a delivery. If a delivery was consumed successfully, it will be deleted from AWS SQS\nfunc (b *Broker) consumeOne(delivery *awssqs.ReceiveMessageOutput, taskProcessor iface.TaskProcessor) error {\n\tif len(delivery.Messages) == 0 {\n\t\tlog.ERROR.Printf(\"received an empty message, the delivery was %v\", delivery)\n\t\treturn errors.New(\"received empty message, the delivery is \" + delivery.GoString())\n\t}\n\n\tsig := new(tasks.Signature)\n\tdecoder := json.NewDecoder(strings.NewReader(*delivery.Messages[0].Body))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(sig); err != nil {\n\t\tlog.ERROR.Printf(\"unmarshal error. the delivery is %v\", delivery)\n\t\t// if the unmarshal fails, remove the delivery from the queue\n\t\tif delErr := b.deleteOne(delivery); delErr != nil {\n\t\t\tlog.ERROR.Printf(\"error when deleting the delivery. delivery is %v, Error=%s\", delivery, delErr)\n\t\t}\n\t\treturn err\n\t}\n\tif delivery.Messages[0].ReceiptHandle != nil {\n\t\tsig.SQSReceiptHandle = *delivery.Messages[0].ReceiptHandle\n\t}\n\n\t// If the task is not registered return an error\n\t// and leave the message in the queue\n\tif !b.IsTaskRegistered(sig.Name) {\n\t\tif sig.IgnoreWhenTaskNotRegistered {\n\t\t\tb.deleteOne(delivery)\n\t\t}\n\t\treturn fmt.Errorf(\"task %s is not registered\", sig.Name)\n\t}\n\n\terr := taskProcessor.Process(sig)\n\tif err != nil {\n\t\t// stop task deletion in case we want to send messages to dlq in sqs\n\t\tif err == errs.ErrStopTaskDeletion {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\t// Delete message after successfully consuming and processing the message\n\tif err = b.deleteOne(delivery); err != nil {\n\t\tlog.ERROR.Printf(\"error when deleting the delivery. delivery is %v, Error=%s\", delivery, err)\n\t}\n\treturn err\n}\n\n// deleteOne is a method delete a delivery from AWS SQS\nfunc (b *Broker) deleteOne(delivery *awssqs.ReceiveMessageOutput) error {\n\tqURL := b.defaultQueueURL()\n\t_, err := b.service.DeleteMessage(&awssqs.DeleteMessageInput{\n\t\tQueueUrl:      qURL,\n\t\tReceiptHandle: delivery.Messages[0].ReceiptHandle,\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// defaultQueueURL is a method returns the default queue url\nfunc (b *Broker) defaultQueueURL() *string {\n\tif b.queueUrl != nil {\n\t\treturn b.queueUrl\n\t} else {\n\t\treturn aws.String(b.GetConfig().Broker + \"/\" + b.GetConfig().DefaultQueue)\n\t}\n\n}\n\n// receiveMessage is a method receives a message from specified queue url\nfunc (b *Broker) receiveMessage(qURL *string) (*awssqs.ReceiveMessageOutput, error) {\n\tvar waitTimeSeconds int\n\tvar visibilityTimeout *int\n\tif b.GetConfig().SQS != nil {\n\t\twaitTimeSeconds = b.GetConfig().SQS.WaitTimeSeconds\n\t\tvisibilityTimeout = b.GetConfig().SQS.VisibilityTimeout\n\t} else {\n\t\twaitTimeSeconds = 0\n\t}\n\tinput := &awssqs.ReceiveMessageInput{\n\t\tAttributeNames: []*string{\n\t\t\taws.String(awssqs.MessageSystemAttributeNameSentTimestamp),\n\t\t},\n\t\tMessageAttributeNames: []*string{\n\t\t\taws.String(awssqs.QueueAttributeNameAll),\n\t\t},\n\t\tQueueUrl:            qURL,\n\t\tMaxNumberOfMessages: aws.Int64(1),\n\t\tWaitTimeSeconds:     aws.Int64(int64(waitTimeSeconds)),\n\t}\n\tif visibilityTimeout != nil {\n\t\tinput.VisibilityTimeout = aws.Int64(int64(*visibilityTimeout))\n\t}\n\tresult, err := b.service.ReceiveMessage(input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn result, err\n}\n\n// initializePool is a method which initializes concurrency pool\nfunc (b *Broker) initializePool(pool chan struct{}, concurrency int) {\n\tfor i := 0; i < concurrency; i++ {\n\t\tpool <- struct{}{}\n\t}\n}\n\n// consumeDeliveries is a method consuming deliveries from deliveries channel\nfunc (b *Broker) consumeDeliveries(deliveries <-chan *awssqs.ReceiveMessageOutput, concurrency int, taskProcessor iface.TaskProcessor, pool chan struct{}, errorsChan chan error) (bool, error) {\n\tselect {\n\tcase err := <-errorsChan:\n\t\treturn false, err\n\tcase d := <-deliveries:\n\n\t\tb.processingWG.Add(1)\n\n\t\t// Consume the task inside a goroutine so multiple tasks\n\t\t// can be processed concurrently\n\t\tgo func() {\n\n\t\t\tif err := b.consumeOne(d, taskProcessor); err != nil {\n\t\t\t\terrorsChan <- err\n\t\t\t}\n\n\t\t\tb.processingWG.Done()\n\n\t\t\tif concurrency > 0 {\n\t\t\t\t// give worker back to pool\n\t\t\t\tpool <- struct{}{}\n\t\t\t}\n\t\t}()\n\tcase <-b.GetStopChan():\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\n// continueReceivingMessages is a method returns a continue signal\nfunc (b *Broker) continueReceivingMessages(qURL *string, deliveries chan *awssqs.ReceiveMessageOutput) (bool, error) {\n\tselect {\n\t// A way to stop this goroutine from b.StopConsuming\n\tcase <-b.stopReceivingChan:\n\t\treturn false, nil\n\tdefault:\n\t\toutput, err := b.receiveMessage(qURL)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t\tif len(output.Messages) == 0 {\n\t\t\treturn true, nil\n\t\t}\n\t\tgo func() { deliveries <- output }()\n\t}\n\treturn true, nil\n}\n\n// stopReceiving is a method sending a signal to stopReceivingChan\nfunc (b *Broker) stopReceiving() {\n\t// Stop the receiving goroutine\n\tb.stopReceivingChan <- 1\n}\n\n// getQueueURL is a method returns that returns queueURL first by checking if custom queue was set and usign it\n// otherwise using default queueName from config\nfunc (b *Broker) getQueueURL(taskProcessor iface.TaskProcessor) *string {\n\tqueueName := b.GetConfig().DefaultQueue\n\tif taskProcessor.CustomQueue() != \"\" {\n\t\tqueueName = taskProcessor.CustomQueue()\n\t}\n\n\treturn aws.String(b.GetConfig().Broker + \"/\" + queueName)\n}\n"
  },
  {
    "path": "v1/brokers/sqs/sqs_export_test.go",
    "content": "package sqs\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/sqs/sqsiface\"\n\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\n\tawssqs \"github.com/aws/aws-sdk-go/service/sqs\"\n)\n\nvar (\n\tReceiveMessageOutput *awssqs.ReceiveMessageOutput\n)\n\ntype FakeSQS struct {\n\tsqsiface.SQSAPI\n}\n\nfunc (f *FakeSQS) SendMessage(*awssqs.SendMessageInput) (*awssqs.SendMessageOutput, error) {\n\toutput := awssqs.SendMessageOutput{\n\t\tMD5OfMessageAttributes: aws.String(\"d25a6aea97eb8f585bfa92d314504a92\"),\n\t\tMD5OfMessageBody:       aws.String(\"bbdc5fdb8be7251f5c910905db994bab\"),\n\t\tMessageId:              aws.String(\"47f8b355-5115-4b45-b33a-439016400411\"),\n\t}\n\treturn &output, nil\n}\n\nfunc (f *FakeSQS) ReceiveMessage(*awssqs.ReceiveMessageInput) (*awssqs.ReceiveMessageOutput, error) {\n\treturn ReceiveMessageOutput, nil\n}\n\nfunc (f *FakeSQS) DeleteMessage(*awssqs.DeleteMessageInput) (*awssqs.DeleteMessageOutput, error) {\n\treturn &awssqs.DeleteMessageOutput{}, nil\n}\n\ntype ErrorSQS struct {\n\tsqsiface.SQSAPI\n}\n\nfunc (e *ErrorSQS) SendMessage(*awssqs.SendMessageInput) (*awssqs.SendMessageOutput, error) {\n\terr := errors.New(\"this is an error\")\n\treturn nil, err\n}\n\nfunc (e *ErrorSQS) ReceiveMessage(*awssqs.ReceiveMessageInput) (*awssqs.ReceiveMessageOutput, error) {\n\terr := errors.New(\"this is an error\")\n\treturn nil, err\n}\n\nfunc (e *ErrorSQS) DeleteMessage(*awssqs.DeleteMessageInput) (*awssqs.DeleteMessageOutput, error) {\n\terr := errors.New(\"this is an error\")\n\treturn nil, err\n}\n\nfunc init() {\n\t// TODO: chang message body to signature example\n\tmessageBody, _ := json.Marshal(map[string]int{\"apple\": 5, \"lettuce\": 7})\n\tReceiveMessageOutput = &awssqs.ReceiveMessageOutput{\n\t\tMessages: []*awssqs.Message{\n\t\t\t{\n\t\t\t\tAttributes: map[string]*string{\n\t\t\t\t\t\"SentTimestamp\": aws.String(\"1512962021537\"),\n\t\t\t\t},\n\t\t\t\tBody:                   aws.String(string(messageBody)),\n\t\t\t\tMD5OfBody:              aws.String(\"bbdc5fdb8be7251f5c910905db994bab\"),\n\t\t\t\tMD5OfMessageAttributes: aws.String(\"d25a6aea97eb8f585bfa92d314504a92\"),\n\t\t\t\tMessageAttributes: map[string]*awssqs.MessageAttributeValue{\n\t\t\t\t\t\"Title\": {\n\t\t\t\t\t\tDataType:    aws.String(\"String\"),\n\t\t\t\t\t\tStringValue: aws.String(\"The Whistler\"),\n\t\t\t\t\t},\n\t\t\t\t\t\"Author\": {\n\t\t\t\t\t\tDataType:    aws.String(\"String\"),\n\t\t\t\t\t\tStringValue: aws.String(\"John Grisham\"),\n\t\t\t\t\t},\n\t\t\t\t\t\"WeeksOn\": {\n\t\t\t\t\t\tDataType:    aws.String(\"Number\"),\n\t\t\t\t\t\tStringValue: aws.String(\"6\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMessageId:     aws.String(\"47f8b355-5115-4b45-b33a-439016400411\"),\n\t\t\t\tReceiptHandle: aws.String(\"AQEBGhTR/nhq+pDPAunCDgLpwQuCq0JkD2dtv7pAcPF5DA/XaoPAjHfgn/PZ5DeG3YiQdTjCUj+rvFq5b79DTq+hK6r1Niuds02l+jdIk3u2JiL01Dsd203pW1lLUNryd74QAcn462eXzv7/hVDagXTn+KtOzox3X0vmPkCSQkWXWxtc23oa5+5Q7HWDmRm743L0zza1579rQ2R2B0TrdlTMpNsdjQlDmybNu+aDq8bazD/Wew539tIvUyYADuhVyKyS1L2QQuyXll73/DixulPNmvGPRHNoB1GIo+Ex929OHFchXoKonoFJnurX4VNNl1p/Byp2IYBi6nkTRzeJUFCrFq0WMAHKLwuxciezJSlLD7g3bbU8kgEer8+jTz1DBriUlDGsARr0s7mnlsd02cb46K/j+u1oPfA69vIVc0FaRtA=\"),\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc NewTestConfig() *config.Config {\n\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tredisURL = \"eager\"\n\t}\n\tbrokerURL := \"https://sqs.foo.amazonaws.com.cn\"\n\treturn &config.Config{\n\t\tBroker:        brokerURL,\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t}\n}\n\nfunc NewTestBroker() *Broker {\n\n\tcnf := NewTestConfig()\n\tsess := session.Must(session.NewSessionWithOptions(session.Options{\n\t\tSharedConfigState: session.SharedConfigEnable,\n\t}))\n\n\tsvc := new(FakeSQS)\n\treturn &Broker{\n\t\tBroker:            common.NewBroker(cnf),\n\t\tsess:              sess,\n\t\tservice:           svc,\n\t\tprocessingWG:      sync.WaitGroup{},\n\t\treceivingWG:       sync.WaitGroup{},\n\t\tstopReceivingChan: make(chan int),\n\t}\n}\n\nfunc NewTestErrorBroker() *Broker {\n\n\tcnf := NewTestConfig()\n\tsess := session.Must(session.NewSessionWithOptions(session.Options{\n\t\tSharedConfigState: session.SharedConfigEnable,\n\t}))\n\n\terrSvc := new(ErrorSQS)\n\treturn &Broker{\n\t\tBroker:            common.NewBroker(cnf),\n\t\tsess:              sess,\n\t\tservice:           errSvc,\n\t\tprocessingWG:      sync.WaitGroup{},\n\t\treceivingWG:       sync.WaitGroup{},\n\t\tstopReceivingChan: make(chan int),\n\t}\n}\n\nfunc (b *Broker) ConsumeForTest(deliveries <-chan *awssqs.ReceiveMessageOutput, concurrency int, taskProcessor iface.TaskProcessor, pool chan struct{}) error {\n\treturn b.consume(deliveries, concurrency, taskProcessor, pool)\n}\n\nfunc (b *Broker) ConsumeOneForTest(delivery *awssqs.ReceiveMessageOutput, taskProcessor iface.TaskProcessor) error {\n\treturn b.consumeOne(delivery, taskProcessor)\n}\n\nfunc (b *Broker) DeleteOneForTest(delivery *awssqs.ReceiveMessageOutput) error {\n\treturn b.deleteOne(delivery)\n}\n\nfunc (b *Broker) DefaultQueueURLForTest() *string {\n\treturn b.defaultQueueURL()\n}\n\nfunc (b *Broker) ReceiveMessageForTest(qURL *string) (*awssqs.ReceiveMessageOutput, error) {\n\treturn b.receiveMessage(qURL)\n}\n\nfunc (b *Broker) InitializePoolForTest(pool chan struct{}, concurrency int) {\n\tb.initializePool(pool, concurrency)\n}\n\nfunc (b *Broker) ConsumeDeliveriesForTest(deliveries <-chan *awssqs.ReceiveMessageOutput, concurrency int, taskProcessor iface.TaskProcessor, pool chan struct{}, errorsChan chan error) (bool, error) {\n\treturn b.consumeDeliveries(deliveries, concurrency, taskProcessor, pool, errorsChan)\n}\n\nfunc (b *Broker) ContinueReceivingMessagesForTest(qURL *string, deliveries chan *awssqs.ReceiveMessageOutput) (bool, error) {\n\treturn b.continueReceivingMessages(qURL, deliveries)\n}\n\nfunc (b *Broker) StopReceivingForTest() {\n\tb.stopReceiving()\n}\n\nfunc (b *Broker) GetStopReceivingChanForTest() chan int {\n\treturn b.stopReceivingChan\n}\n\nfunc (b *Broker) StartConsumingForTest(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) {\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n}\n\nfunc (b *Broker) GetRetryFuncForTest() func(chan int) {\n\treturn b.GetRetryFunc()\n}\n\nfunc (b *Broker) GetStopChanForTest() chan int {\n\treturn b.GetStopChan()\n}\n\nfunc (b *Broker) GetRetryStopChanForTest() chan int {\n\treturn b.GetRetryStopChan()\n}\n\nfunc (b *Broker) GetQueueURLForTest(taskProcessor iface.TaskProcessor) *string {\n\treturn b.getQueueURL(taskProcessor)\n}\n\nfunc (b *Broker) GetCustomQueueURL(customQueue string) *string {\n\treturn aws.String(b.GetConfig().Broker + \"/\" + customQueue)\n}\n"
  },
  {
    "path": "v1/brokers/sqs/sqs_test.go",
    "content": "package sqs_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/sqs\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/retry\"\n\n\tawssqs \"github.com/aws/aws-sdk-go/service/sqs\"\n)\n\nvar (\n\tcnf                  *config.Config\n\treceiveMessageOutput *awssqs.ReceiveMessageOutput\n)\n\nfunc init() {\n\tcnf = sqs.NewTestConfig()\n\treceiveMessageOutput = sqs.ReceiveMessageOutput\n}\n\nfunc TestNewAWSSQSBroker(t *testing.T) {\n\tt.Parallel()\n\n\tbroker := sqs.NewTestBroker()\n\n\tassert.IsType(t, broker, sqs.New(cnf))\n}\n\nfunc TestPrivateFunc_continueReceivingMessages(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker()\n\terrorBroker := sqs.NewTestErrorBroker()\n\n\tqURL := broker.DefaultQueueURLForTest()\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput)\n\tfirstStep := make(chan int)\n\tnextStep := make(chan int)\n\tgo func() {\n\t\tstopReceivingChan := broker.GetStopReceivingChanForTest()\n\t\tfirstStep <- 1\n\t\tstopReceivingChan <- 1\n\t}()\n\n\tvar (\n\t\twhetherContinue bool\n\t\terr             error\n\t)\n\t<-firstStep\n\t// Test the case that a signal was received from stopReceivingChan\n\tgo func() {\n\t\twhetherContinue, err = broker.ContinueReceivingMessagesForTest(qURL, deliveries)\n\t\tnextStep <- 1\n\t}()\n\t<-nextStep\n\tassert.False(t, whetherContinue)\n\tassert.Nil(t, err)\n\n\t// Test the default condition\n\twhetherContinue, err = broker.ContinueReceivingMessagesForTest(qURL, deliveries)\n\tassert.True(t, whetherContinue)\n\tassert.Nil(t, err)\n\n\t// Test the error\n\twhetherContinue, err = errorBroker.ContinueReceivingMessagesForTest(qURL, deliveries)\n\tassert.True(t, whetherContinue)\n\tassert.NotNil(t, err)\n\n\t// Test when there is no message\n\toutputCopy := *receiveMessageOutput\n\treceiveMessageOutput.Messages = []*awssqs.Message{}\n\twhetherContinue, err = broker.ContinueReceivingMessagesForTest(qURL, deliveries)\n\tassert.True(t, whetherContinue)\n\tassert.Nil(t, err)\n\t// recover original value\n\t*receiveMessageOutput = outputCopy\n}\n\nfunc TestPrivateFunc_consume(t *testing.T) {\n\n\tserver1, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpool := make(chan struct{})\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput)\n\toutputCopy := *receiveMessageOutput\n\toutputCopy.Messages = []*awssqs.Message{}\n\tgo func() { deliveries <- &outputCopy }()\n\n\tbroker := sqs.NewTestBroker()\n\n\t// an infinite loop will be executed only when there is no error\n\terr = broker.ConsumeForTest(deliveries, 0, wk, pool)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFunc_consumeOne(t *testing.T) {\n\n\tserver1, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\tbroker := sqs.NewTestBroker()\n\n\terr = broker.ConsumeOneForTest(receiveMessageOutput, wk)\n\tassert.NotNil(t, err)\n\n\toutputCopy := *receiveMessageOutput\n\toutputCopy.Messages = []*awssqs.Message{}\n\terr = broker.ConsumeOneForTest(&outputCopy, wk)\n\tassert.NotNil(t, err)\n\n\toutputCopy.Messages = []*awssqs.Message{\n\t\t{\n\t\t\tBody: aws.String(\"foo message\"),\n\t\t},\n\t}\n\terr = broker.ConsumeOneForTest(&outputCopy, wk)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFunc_initializePool(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker()\n\n\tconcurrency := 9\n\tpool := make(chan struct{}, concurrency)\n\tbroker.InitializePoolForTest(pool, concurrency)\n\tassert.Len(t, pool, concurrency)\n}\n\nfunc TestPrivateFunc_startConsuming(t *testing.T) {\n\n\tserver1, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\tbroker := sqs.NewTestBroker()\n\n\tretryFunc := broker.GetRetryFuncForTest()\n\tstopChan := broker.GetStopChanForTest()\n\tretryStopChan := broker.GetRetryStopChanForTest()\n\tassert.Nil(t, retryFunc)\n\n\tbroker.StartConsumingForTest(\"fooTag\", 1, wk)\n\tassert.IsType(t, retryFunc, retry.Closure())\n\tassert.Equal(t, len(stopChan), 0)\n\tassert.Equal(t, len(retryStopChan), 0)\n}\n\nfunc TestPrivateFuncDefaultQueueURL(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker()\n\n\tqURL := broker.DefaultQueueURLForTest()\n\n\tassert.EqualValues(t, *qURL, \"https://sqs.foo.amazonaws.com.cn/test_queue\")\n}\n\nfunc TestPrivateFunc_stopReceiving(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker()\n\n\tgo broker.StopReceivingForTest()\n\n\tstopReceivingChan := broker.GetStopReceivingChanForTest()\n\tassert.NotNil(t, <-stopReceivingChan)\n}\n\nfunc TestPrivateFunc_receiveMessage(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker()\n\n\tqURL := broker.DefaultQueueURLForTest()\n\toutput, err := broker.ReceiveMessageForTest(qURL)\n\tassert.Nil(t, err)\n\tassert.Equal(t, receiveMessageOutput, output)\n}\n\nfunc TestPrivateFunc_consumeDeliveries(t *testing.T) {\n\n\tconcurrency := 0\n\tpool := make(chan struct{}, concurrency)\n\terrorsChan := make(chan error)\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput)\n\tserver1, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\tbroker := sqs.NewTestBroker()\n\n\tgo func() { deliveries <- receiveMessageOutput }()\n\twhetherContinue, err := broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\tassert.True(t, whetherContinue)\n\tassert.Nil(t, err)\n\n\tgo func() { errorsChan <- errors.New(\"foo error\") }()\n\twhetherContinue, err = broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\tassert.False(t, whetherContinue)\n\tassert.NotNil(t, err)\n\n\tgo func() { broker.GetStopChanForTest() <- 1 }()\n\twhetherContinue, err = broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\tassert.False(t, whetherContinue)\n\tassert.Nil(t, err)\n\n\toutputCopy := *receiveMessageOutput\n\toutputCopy.Messages = []*awssqs.Message{}\n\tgo func() { deliveries <- &outputCopy }()\n\twhetherContinue, err = broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\te := <-errorsChan\n\tassert.True(t, whetherContinue)\n\tassert.NotNil(t, e)\n\tassert.Nil(t, err)\n\n\t// using a wait group and a channel to fix the racing problem\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tnextStep := make(chan bool, 1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t// nextStep <- true runs after defer wg.Done(), to make sure the next go routine runs after this go routine\n\t\tnextStep <- true\n\t\tdeliveries <- receiveMessageOutput\n\t}()\n\tif <-nextStep {\n\t\t// <-pool will block the routine in the following steps, so pool <- struct{}{} will be executed for sure\n\t\tgo func() { wg.Wait(); pool <- struct{}{} }()\n\t}\n\twhetherContinue, err = broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\t// the pool shouldn't be consumed\n\tp := <-pool\n\tassert.True(t, whetherContinue)\n\tassert.NotNil(t, p)\n\tassert.Nil(t, err)\n}\n\nfunc TestPrivateFunc_deleteOne(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker()\n\terrorBroker := sqs.NewTestErrorBroker()\n\n\terr := broker.DeleteOneForTest(receiveMessageOutput)\n\tassert.Nil(t, err)\n\n\terr = errorBroker.DeleteOneForTest(receiveMessageOutput)\n\tassert.NotNil(t, err)\n}\n\nfunc Test_CustomQueueName(t *testing.T) {\n\n\tserver1, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tbroker := sqs.NewTestBroker()\n\n\twk := server1.NewWorker(\"test-worker\", 0)\n\tqURL := broker.GetQueueURLForTest(wk)\n\tassert.Equal(t, qURL, broker.DefaultQueueURLForTest(), \"\")\n\n\twk2 := server1.NewCustomQueueWorker(\"test-worker\", 0, \"my-custom-queue\")\n\tqURL2 := broker.GetQueueURLForTest(wk2)\n\tassert.Equal(t, qURL2, broker.GetCustomQueueURL(\"my-custom-queue\"), \"\")\n}\n\nfunc TestPrivateFunc_consumeWithConcurrency(t *testing.T) {\n\n\tmsg := `{\n        \"UUID\": \"uuid-dummy-task\",\n        \"Name\": \"test-task\",\n        \"RoutingKey\": \"dummy-routing\"\n\t}\n\t`\n\n\ttestResp := \"47f8b355-5115-4b45-b33a-439016400411\"\n\toutput := make(chan string) // The output channel\n\n\tcnf.ResultBackend = \"eager\"\n\tserver1, err := machinery.NewServer(cnf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = server1.RegisterTask(\"test-task\", func(ctx context.Context) error {\n\t\toutput <- testResp\n\n\t\treturn nil\n\t})\n\n\tbroker := sqs.NewTestBroker()\n\n\tbroker.SetRegisteredTaskNames([]string{\"test-task\"})\n\tassert.NoError(t, err)\n\tpool := make(chan struct{}, 1)\n\tpool <- struct{}{}\n\twk := server1.NewWorker(\"sms_worker\", 1)\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput)\n\toutputCopy := *receiveMessageOutput\n\toutputCopy.Messages = []*awssqs.Message{\n\t\t{\n\t\t\tMessageId: aws.String(\"test-sqs-msg1\"),\n\t\t\tBody:      aws.String(msg),\n\t\t},\n\t}\n\n\tgo func() {\n\t\tdeliveries <- &outputCopy\n\n\t}()\n\n\tgo func() {\n\t\terr = broker.ConsumeForTest(deliveries, 1, wk, pool)\n\t}()\n\n\tselect {\n\tcase resp := <-output:\n\t\tassert.Equal(t, testResp, resp)\n\n\tcase <-time.After(10 * time.Second):\n\t\t// call timed out\n\t\tt.Fatal(\"task not processed in 10 seconds\")\n\t}\n}\n"
  },
  {
    "path": "v1/common/amqp.go",
    "content": "package common\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"strings\"\n\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\n// AMQPConnector ...\ntype AMQPConnector struct{}\n\n// Connect opens a connection to RabbitMQ, declares an exchange, opens a channel,\n// declares and binds the queue and enables publish notifications\nfunc (ac *AMQPConnector) Connect(urls string, urlSeparator string, tlsConfig *tls.Config, exchange, exchangeType, queueName string, queueDurable, queueDelete bool, queueBindingKey string, exchangeDeclareArgs, queueDeclareArgs, queueBindingArgs amqp.Table) (*amqp.Connection, *amqp.Channel, amqp.Queue, <-chan amqp.Confirmation, <-chan *amqp.Error, error) {\n\turlsList := []string{urls}\n\tif urlSeparator != \"\" {\n\t\turlsList = strings.Split(urls, urlSeparator)\n\t}\n\n\tvar conn *amqp.Connection\n\tvar channel *amqp.Channel\n\tvar err error\n\n\tfor _, url := range urlsList {\n\t\t// Connect to server\n\t\tconn, channel, err = ac.Open(url, tlsConfig)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn nil, nil, amqp.Queue{}, nil, nil, err\n\t}\n\n\tif exchange != \"\" {\n\t\t// Declare an exchange\n\t\tif err = channel.ExchangeDeclare(\n\t\t\texchange,            // name of the exchange\n\t\t\texchangeType,        // type\n\t\t\ttrue,                // durable\n\t\t\tfalse,               // delete when complete\n\t\t\tfalse,               // internal\n\t\t\tfalse,               // noWait\n\t\t\texchangeDeclareArgs, // arguments\n\t\t); err != nil {\n\t\t\treturn conn, channel, amqp.Queue{}, nil, nil, fmt.Errorf(\"Exchange declare error: %s\", err)\n\t\t}\n\t}\n\n\tvar queue amqp.Queue\n\tif queueName != \"\" {\n\t\t// Declare a queue\n\t\tqueue, err = channel.QueueDeclare(\n\t\t\tqueueName,        // name\n\t\t\tqueueDurable,     // durable\n\t\t\tqueueDelete,      // delete when unused\n\t\t\tfalse,            // exclusive\n\t\t\tfalse,            // no-wait\n\t\t\tqueueDeclareArgs, // arguments\n\t\t)\n\t\tif err != nil {\n\t\t\treturn conn, channel, amqp.Queue{}, nil, nil, fmt.Errorf(\"Queue declare error: %s\", err)\n\t\t}\n\n\t\t// Bind the queue\n\t\tif err = channel.QueueBind(\n\t\t\tqueue.Name,       // name of the queue\n\t\t\tqueueBindingKey,  // binding key\n\t\t\texchange,         // source exchange\n\t\t\tfalse,            // noWait\n\t\t\tqueueBindingArgs, // arguments\n\t\t); err != nil {\n\t\t\treturn conn, channel, queue, nil, nil, fmt.Errorf(\"Queue bind error: %s\", err)\n\t\t}\n\t}\n\n\t// Enable publish confirmations\n\tif err = channel.Confirm(false); err != nil {\n\t\treturn conn, channel, queue, nil, nil, fmt.Errorf(\"Channel could not be put into confirm mode: %s\", err)\n\t}\n\n\treturn conn, channel, queue, channel.NotifyPublish(make(chan amqp.Confirmation, 1)), conn.NotifyClose(make(chan *amqp.Error, 1)), nil\n}\n\n// DeleteQueue deletes a queue by name\nfunc (ac *AMQPConnector) DeleteQueue(channel *amqp.Channel, queueName string) error {\n\t// First return value is number of messages removed\n\t_, err := channel.QueueDelete(\n\t\tqueueName, // name\n\t\tfalse,     // ifUnused\n\t\tfalse,     // ifEmpty\n\t\tfalse,     // noWait\n\t)\n\n\treturn err\n}\n\n// InspectQueue provides information about a specific queue\nfunc (*AMQPConnector) InspectQueue(channel *amqp.Channel, queueName string) (*amqp.Queue, error) {\n\tqueueState, err := channel.QueueInspect(queueName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Queue inspect error: %s\", err)\n\t}\n\n\treturn &queueState, nil\n}\n\n// Open new RabbitMQ connection\nfunc (ac *AMQPConnector) Open(url string, tlsConfig *tls.Config) (*amqp.Connection, *amqp.Channel, error) {\n\t// Connect\n\t// From amqp docs: DialTLS will use the provided tls.Config when it encounters an amqps:// scheme\n\t// and will dial a plain connection when it encounters an amqp:// scheme.\n\tconn, err := amqp.DialTLS(url, tlsConfig)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"Dial error: %s\", err)\n\t}\n\n\t// Open a channel\n\tchannel, err := conn.Channel()\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"Open channel error: %s\", err)\n\t}\n\n\treturn conn, channel, nil\n}\n\n// Close connection\nfunc (ac *AMQPConnector) Close(channel *amqp.Channel, conn *amqp.Connection) error {\n\tif channel != nil {\n\t\tif err := channel.Close(); err != nil {\n\t\t\treturn fmt.Errorf(\"Close channel error: %s\", err)\n\t\t}\n\t}\n\n\tif conn != nil {\n\t\tif err := conn.Close(); err != nil {\n\t\t\treturn fmt.Errorf(\"Close connection error: %s\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "v1/common/backend.go",
    "content": "package common\n\nimport (\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\n// Backend represents a base backend structure\ntype Backend struct {\n\tcnf *config.Config\n}\n\n// NewBackend creates new Backend instance\nfunc NewBackend(cnf *config.Config) Backend {\n\treturn Backend{cnf: cnf}\n}\n\n// GetConfig returns config\nfunc (b *Backend) GetConfig() *config.Config {\n\treturn b.cnf\n}\n\n// IsAMQP ...\nfunc (b *Backend) IsAMQP() bool {\n\treturn false\n}\n"
  },
  {
    "path": "v1/common/broker.go",
    "content": "package common\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/retry\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\ntype registeredTaskNames struct {\n\tsync.RWMutex\n\titems []string\n}\n\n// Broker represents a base broker structure\ntype Broker struct {\n\tcnf                 *config.Config\n\tregisteredTaskNames registeredTaskNames\n\tretry               bool\n\tretryFunc           func(chan int)\n\tretryStopChan       chan int\n\tstopChan            chan int\n}\n\n// NewBroker creates new Broker instance\nfunc NewBroker(cnf *config.Config) Broker {\n\treturn Broker{\n\t\tcnf:           cnf,\n\t\tretry:         true,\n\t\tstopChan:      make(chan int),\n\t\tretryStopChan: make(chan int),\n\t}\n}\n\n// GetConfig returns config\nfunc (b *Broker) GetConfig() *config.Config {\n\treturn b.cnf\n}\n\n// GetRetry ...\nfunc (b *Broker) GetRetry() bool {\n\treturn b.retry\n}\n\n// GetRetryFunc ...\nfunc (b *Broker) GetRetryFunc() func(chan int) {\n\treturn b.retryFunc\n}\n\n// GetRetryStopChan ...\nfunc (b *Broker) GetRetryStopChan() chan int {\n\treturn b.retryStopChan\n}\n\n// GetStopChan ...\nfunc (b *Broker) GetStopChan() chan int {\n\treturn b.stopChan\n}\n\n// Publish places a new message on the default queue\nfunc (b *Broker) Publish(signature *tasks.Signature) error {\n\treturn errors.New(\"Not implemented\")\n}\n\n// SetRegisteredTaskNames sets registered task names\nfunc (b *Broker) SetRegisteredTaskNames(names []string) {\n\tb.registeredTaskNames.Lock()\n\tdefer b.registeredTaskNames.Unlock()\n\tb.registeredTaskNames.items = names\n}\n\n// IsTaskRegistered returns true if the task is registered with this broker\nfunc (b *Broker) IsTaskRegistered(name string) bool {\n\tb.registeredTaskNames.RLock()\n\tdefer b.registeredTaskNames.RUnlock()\n\tfor _, registeredTaskName := range b.registeredTaskNames.items {\n\t\tif registeredTaskName == name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// GetPendingTasks returns a slice of task.Signatures waiting in the queue\nfunc (b *Broker) GetPendingTasks(queue string) ([]*tasks.Signature, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\n// GetDelayedTasks returns a slice of task.Signatures that are scheduled, but not yet in the queue\nfunc (b *Broker) GetDelayedTasks() ([]*tasks.Signature, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\n// StartConsuming is a common part of StartConsuming method\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) {\n\tif b.retryFunc == nil {\n\t\tb.retryFunc = retry.Closure()\n\t}\n\n}\n\n// StopConsuming is a common part of StopConsuming\nfunc (b *Broker) StopConsuming() {\n\t// Do not retry from now on\n\tb.retry = false\n\t// Stop the retry closure earlier\n\tselect {\n\tcase b.retryStopChan <- 1:\n\t\tlog.WARNING.Print(\"Stopping retry closure.\")\n\tdefault:\n\t}\n\t// Notifying the stop channel stops consuming of messages\n\tclose(b.stopChan)\n\tlog.WARNING.Print(\"Stop channel\")\n}\n\n// GetRegisteredTaskNames returns registered tasks names\nfunc (b *Broker) GetRegisteredTaskNames() []string {\n\tb.registeredTaskNames.RLock()\n\tdefer b.registeredTaskNames.RUnlock()\n\titems := b.registeredTaskNames.items\n\treturn items\n}\n\n// AdjustRoutingKey makes sure the routing key is correct.\n// If the routing key is an empty string:\n// a) set it to binding key for direct exchange type\n// b) set it to default queue name\nfunc (b *Broker) AdjustRoutingKey(s *tasks.Signature) {\n\tif s.RoutingKey != \"\" {\n\t\treturn\n\t}\n\n\ts.RoutingKey = b.GetConfig().DefaultQueue\n}\n"
  },
  {
    "path": "v1/common/broker_test.go",
    "content": "package common_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/common\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestIsTaskRegistered(t *testing.T) {\n\tt.Parallel()\n\n\tbroker := common.NewBroker(new(config.Config))\n\tbroker.SetRegisteredTaskNames([]string{\"foo\", \"bar\"})\n\n\tassert.True(t, broker.IsTaskRegistered(\"foo\"))\n\tassert.False(t, broker.IsTaskRegistered(\"bogus\"))\n}\n\nfunc TestAdjustRoutingKey(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\ts      *tasks.Signature\n\t\tbroker common.Broker\n\t)\n\n\tt.Run(\"with routing key\", func(t *testing.T) {\n\t\ts = &tasks.Signature{RoutingKey: \"routing_key\"}\n\t\tbroker = common.NewBroker(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t})\n\t\tbroker.AdjustRoutingKey(s)\n\t\tassert.Equal(t, \"routing_key\", s.RoutingKey)\n\t})\n\n\tt.Run(\"without routing key\", func(t *testing.T) {\n\t\ts = new(tasks.Signature)\n\t\tbroker = common.NewBroker(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t})\n\t\tbroker.AdjustRoutingKey(s)\n\t\tassert.Equal(t, \"queue\", s.RoutingKey)\n\t})\n}\n\nfunc TestGetRegisteredTaskNames(t *testing.T) {\n\tt.Parallel()\n\n\tbroker := common.NewBroker(new(config.Config))\n\tfooTasks := []string{\"foo\", \"bar\", \"baz\"}\n\tbroker.SetRegisteredTaskNames(fooTasks)\n\tassert.Equal(t, fooTasks, broker.GetRegisteredTaskNames())\n}\n\nfunc TestStopConsuming(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"stop consuming\", func(t *testing.T) {\n\t\tbroker := common.NewBroker(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t})\n\t\tbroker.StartConsuming(\"\", 1, &machinery.Worker{})\n\t\tbroker.StopConsuming()\n\t\tselect {\n\t\tcase <-broker.GetStopChan():\n\t\tdefault:\n\t\t\tassert.Fail(t, \"still blocking\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "v1/common/redis.go",
    "content": "package common\n\nimport (\n\t\"crypto/tls\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nvar (\n\tdefaultConfig = &config.RedisConfig{\n\t\tMaxIdle:                10,\n\t\tMaxActive:              100,\n\t\tIdleTimeout:            300,\n\t\tWait:                   true,\n\t\tReadTimeout:            15,\n\t\tWriteTimeout:           15,\n\t\tConnectTimeout:         15,\n\t\tNormalTasksPollPeriod:  1000,\n\t\tDelayedTasksPollPeriod: 20,\n\t}\n)\n\n// RedisConnector ...\ntype RedisConnector struct{}\n\n// NewPool returns a new pool of Redis connections\nfunc (rc *RedisConnector) NewPool(socketPath, host, username, password string, db int, cnf *config.RedisConfig, tlsConfig *tls.Config) *redis.Pool {\n\tif cnf == nil {\n\t\tcnf = defaultConfig\n\t}\n\treturn &redis.Pool{\n\t\tMaxIdle:     cnf.MaxIdle,\n\t\tIdleTimeout: time.Duration(cnf.IdleTimeout) * time.Second,\n\t\tMaxActive:   cnf.MaxActive,\n\t\tWait:        cnf.Wait,\n\t\tDial: func() (redis.Conn, error) {\n\t\t\tc, err := rc.open(socketPath, host, username, password, db, cnf, tlsConfig)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif db != 0 {\n\t\t\t\t_, err = c.Do(\"SELECT\", db)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn c, err\n\t\t},\n\t\t// PINGs connections that have been idle more than 10 seconds\n\t\tTestOnBorrow: func(c redis.Conn, t time.Time) error {\n\t\t\tif time.Since(t) < time.Duration(10*time.Second) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t_, err := c.Do(\"PING\")\n\t\t\treturn err\n\t\t},\n\t}\n}\n\n// Open a new Redis connection\nfunc (rc *RedisConnector) open(socketPath, host, username string, password string, db int, cnf *config.RedisConfig, tlsConfig *tls.Config) (redis.Conn, error) {\n\tvar opts = []redis.DialOption{\n\t\tredis.DialDatabase(db),\n\t\tredis.DialReadTimeout(time.Duration(cnf.ReadTimeout) * time.Second),\n\t\tredis.DialWriteTimeout(time.Duration(cnf.WriteTimeout) * time.Second),\n\t\tredis.DialConnectTimeout(time.Duration(cnf.ConnectTimeout) * time.Second),\n\t\tredis.DialClientName(cnf.ClientName),\n\t}\n\n\tif tlsConfig != nil {\n\t\topts = append(opts, redis.DialTLSConfig(tlsConfig), redis.DialUseTLS(true))\n\t}\n\n\tif username != \"\" {\n\t\topts = append(opts, redis.DialUsername(username))\n\t}\n\n\tif password != \"\" {\n\t\topts = append(opts, redis.DialPassword(password))\n\t}\n\n\tif socketPath != \"\" {\n\t\treturn redis.Dial(\"unix\", socketPath, opts...)\n\t}\n\n\treturn redis.Dial(\"tcp\", host, opts...)\n}\n"
  },
  {
    "path": "v1/config/config.go",
    "content": "package config\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"cloud.google.com/go/pubsub\"\n\t\"github.com/aws/aws-sdk-go/service/dynamodb\"\n\t\"github.com/aws/aws-sdk-go/service/sqs\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n)\n\nconst (\n\t// DefaultResultsExpireIn is a default time used to expire task states and group metadata from the backend\n\tDefaultResultsExpireIn = 3600\n)\n\nvar (\n\t// Start with sensible default values\n\tdefaultCnf = &Config{\n\t\tBroker:          \"amqp://guest:guest@localhost:5672/\",\n\t\tDefaultQueue:    \"machinery_tasks\",\n\t\tResultBackend:   \"amqp://guest:guest@localhost:5672/\",\n\t\tResultsExpireIn: DefaultResultsExpireIn,\n\t\tAMQP: &AMQPConfig{\n\t\t\tExchange:      \"machinery_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"machinery_task\",\n\t\t\tPrefetchCount: 3,\n\t\t},\n\t\tDynamoDB: &DynamoDBConfig{\n\t\t\tTaskStatesTable: \"task_states\",\n\t\t\tGroupMetasTable: \"group_metas\",\n\t\t},\n\t\tRedis: &RedisConfig{\n\t\t\tMaxIdle:                3,\n\t\t\tIdleTimeout:            240,\n\t\t\tReadTimeout:            15,\n\t\t\tWriteTimeout:           15,\n\t\t\tConnectTimeout:         15,\n\t\t\tNormalTasksPollPeriod:  1000,\n\t\t\tDelayedTasksPollPeriod: 500,\n\t\t},\n\t\tGCPPubSub: &GCPPubSubConfig{\n\t\t\tClient: nil,\n\t\t},\n\t}\n\n\treloadDelay = time.Second * 10\n)\n\n// Config holds all configuration for our program\ntype Config struct {\n\tBroker                  string           `yaml:\"broker\" envconfig:\"BROKER\"`\n\tLock                    string           `yaml:\"lock\" envconfig:\"LOCK\"`\n\tMultipleBrokerSeparator string           `yaml:\"multiple_broker_separator\" envconfig:\"MULTIPLE_BROKEN_SEPARATOR\"`\n\tDefaultQueue            string           `yaml:\"default_queue\" envconfig:\"DEFAULT_QUEUE\"`\n\tResultBackend           string           `yaml:\"result_backend\" envconfig:\"RESULT_BACKEND\"`\n\tResultsExpireIn         int              `yaml:\"results_expire_in\" envconfig:\"RESULTS_EXPIRE_IN\"`\n\tAMQP                    *AMQPConfig      `yaml:\"amqp\"`\n\tSQS                     *SQSConfig       `yaml:\"sqs\"`\n\tRedis                   *RedisConfig     `yaml:\"redis\"`\n\tGCPPubSub               *GCPPubSubConfig `yaml:\"-\" ignored:\"true\"`\n\tMongoDB                 *MongoDBConfig   `yaml:\"-\" ignored:\"true\"`\n\tTLSConfig               *tls.Config\n\t// NoUnixSignals - when set disables signal handling in machinery\n\tNoUnixSignals bool            `yaml:\"no_unix_signals\" envconfig:\"NO_UNIX_SIGNALS\"`\n\tDynamoDB      *DynamoDBConfig `yaml:\"dynamodb\"`\n}\n\n// QueueBindingArgs arguments which are used when binding to the exchange\ntype QueueBindingArgs map[string]interface{}\n\n// QueueDeclareArgs arguments which are used when declaring a queue\ntype QueueDeclareArgs map[string]interface{}\n\n// AMQPConfig wraps RabbitMQ related configuration\ntype AMQPConfig struct {\n\tExchange         string           `yaml:\"exchange\" envconfig:\"AMQP_EXCHANGE\"`\n\tExchangeType     string           `yaml:\"exchange_type\" envconfig:\"AMQP_EXCHANGE_TYPE\"`\n\tQueueDeclareArgs QueueDeclareArgs `yaml:\"queue_declare_args\" envconfig:\"AMQP_QUEUE_DECLARE_ARGS\"`\n\tQueueBindingArgs QueueBindingArgs `yaml:\"queue_binding_args\" envconfig:\"AMQP_QUEUE_BINDING_ARGS\"`\n\tBindingKey       string           `yaml:\"binding_key\" envconfig:\"AMQP_BINDING_KEY\"`\n\tPrefetchCount    int              `yaml:\"prefetch_count\" envconfig:\"AMQP_PREFETCH_COUNT\"`\n\tAutoDelete       bool             `yaml:\"auto_delete\" envconfig:\"AMQP_AUTO_DELETE\"`\n\tDelayedQueue     string           `yaml:\"delayed_queue\" envconfig:\"AMQP_DELAYED_QUEUE\"`\n}\n\n// DynamoDBConfig wraps DynamoDB related configuration\ntype DynamoDBConfig struct {\n\tClient          *dynamodb.DynamoDB\n\tTaskStatesTable string `yaml:\"task_states_table\" envconfig:\"TASK_STATES_TABLE\"`\n\tGroupMetasTable string `yaml:\"group_metas_table\" envconfig:\"GROUP_METAS_TABLE\"`\n}\n\n// SQSConfig wraps SQS related configuration\ntype SQSConfig struct {\n\tClient          *sqs.SQS\n\tWaitTimeSeconds int `yaml:\"receive_wait_time_seconds\" envconfig:\"SQS_WAIT_TIME_SECONDS\"`\n\t// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html\n\t// visibility timeout should default to nil to use the overall visibility timeout for the queue\n\tVisibilityTimeout *int `yaml:\"receive_visibility_timeout\" envconfig:\"SQS_VISIBILITY_TIMEOUT\"`\n}\n\n// RedisConfig ...\ntype RedisConfig struct {\n\t// Maximum number of idle connections in the pool.\n\t// Default: 10\n\tMaxIdle int `yaml:\"max_idle\" envconfig:\"REDIS_MAX_IDLE\"`\n\n\t// Maximum number of connections allocated by the pool at a given time.\n\t// When zero, there is no limit on the number of connections in the pool.\n\t// Default: 100\n\tMaxActive int `yaml:\"max_active\" envconfig:\"REDIS_MAX_ACTIVE\"`\n\n\t// Close connections after remaining idle for this duration in seconds. If the value\n\t// is zero, then idle connections are not closed. Applications should set\n\t// the timeout to a value less than the server's timeout.\n\t// Default: 300\n\tIdleTimeout int `yaml:\"max_idle_timeout\" envconfig:\"REDIS_IDLE_TIMEOUT\"`\n\n\t// If Wait is true and the pool is at the MaxActive limit, then Get() waits\n\t// for a connection to be returned to the pool before returning.\n\t// Default: true\n\tWait bool `yaml:\"wait\" envconfig:\"REDIS_WAIT\"`\n\n\t// ReadTimeout specifies the timeout in seconds for reading a single command reply.\n\t// Default: 15\n\tReadTimeout int `yaml:\"read_timeout\" envconfig:\"REDIS_READ_TIMEOUT\"`\n\n\t// WriteTimeout specifies the timeout in seconds for writing a single command.\n\t// Default: 15\n\tWriteTimeout int `yaml:\"write_timeout\" envconfig:\"REDIS_WRITE_TIMEOUT\"`\n\n\t// ConnectTimeout specifies the timeout in seconds for connecting to the Redis server when\n\t// no DialNetDial option is specified.\n\t// Default: 15\n\tConnectTimeout int `yaml:\"connect_timeout\" envconfig:\"REDIS_CONNECT_TIMEOUT\"`\n\n\t// NormalTasksPollPeriod specifies the period in milliseconds when polling redis for normal tasks\n\t// Default: 1000\n\tNormalTasksPollPeriod int `yaml:\"normal_tasks_poll_period\" envconfig:\"REDIS_NORMAL_TASKS_POLL_PERIOD\"`\n\n\t// DelayedTasksPollPeriod specifies the period in milliseconds when polling redis for delayed tasks\n\t// Default: 20\n\tDelayedTasksPollPeriod int    `yaml:\"delayed_tasks_poll_period\" envconfig:\"REDIS_DELAYED_TASKS_POLL_PERIOD\"`\n\tDelayedTasksKey        string `yaml:\"delayed_tasks_key\" envconfig:\"REDIS_DELAYED_TASKS_KEY\"`\n\n\t// ClientName specifies the redis client name to be set when connecting to the Redis server\n\tClientName string `yaml:\"client_name\" envconfig:\"REDIS_CLIENT_NAME\"`\n\n\t// MasterName specifies a redis master name in order to configure a sentinel-backed redis FailoverClient\n\tMasterName string `yaml:\"master_name\" envconfig:\"REDIS_MASTER_NAME\"`\n\t// ClusterMode specifies machinery should use redis cluster client explicitly\n\tClusterMode bool `yaml:\"cluster_mode\" envconfig:\"REDIS_CLUSTER_MODE\"`\n}\n\n// GCPPubSubConfig wraps GCP PubSub related configuration\ntype GCPPubSubConfig struct {\n\tClient       *pubsub.Client\n\tMaxExtension time.Duration\n}\n\n// MongoDBConfig ...\ntype MongoDBConfig struct {\n\tClient   *mongo.Client\n\tDatabase string\n}\n\n// Decode from yaml to map (any field whose type or pointer-to-type implements\n// envconfig.Decoder can control its own deserialization)\nfunc (args *QueueBindingArgs) Decode(value string) error {\n\tpairs := strings.Split(value, \",\")\n\tmp := make(map[string]interface{}, len(pairs))\n\tfor _, pair := range pairs {\n\t\tkvpair := strings.Split(pair, \":\")\n\t\tif len(kvpair) != 2 {\n\t\t\treturn fmt.Errorf(\"invalid map item: %q\", pair)\n\t\t}\n\t\tmp[kvpair[0]] = kvpair[1]\n\t}\n\t*args = QueueBindingArgs(mp)\n\treturn nil\n}\n"
  },
  {
    "path": "v1/config/env.go",
    "content": "package config\n\nimport (\n\t\"github.com/kelseyhightower/envconfig\"\n\n\t\"github.com/RichardKnop/machinery/v1/log\"\n)\n\n// NewFromEnvironment creates a config object from environment variables\nfunc NewFromEnvironment() (*Config, error) {\n\tcnf, err := fromEnvironment()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.INFO.Print(\"Successfully loaded config from the environment\")\n\n\treturn cnf, nil\n}\n\nfunc fromEnvironment() (*Config, error) {\n\tloadedCnf, cnf := new(Config), new(Config)\n\t*cnf = *defaultCnf\n\n\tif err := envconfig.Process(\"\", cnf); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := envconfig.Process(\"\", loadedCnf); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif loadedCnf.AMQP == nil {\n\t\tcnf.AMQP = nil\n\t}\n\n\treturn cnf, nil\n}\n"
  },
  {
    "path": "v1/config/env_test.go",
    "content": "package config_test\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewFromEnvironment(t *testing.T) {\n\tt.Parallel()\n\n\tfile, err := os.Open(\"test.env\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treader := bufio.NewReader(file)\n\tscanner := bufio.NewScanner(reader)\n\tscanner.Split(bufio.ScanLines)\n\tfor scanner.Scan() {\n\t\tparts := strings.Split(scanner.Text(), \"=\")\n\t\tif len(parts) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tos.Setenv(parts[0], parts[1])\n\t}\n\n\tcnf, err := config.NewFromEnvironment()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, \"broker\", cnf.Broker)\n\tassert.Equal(t, \"default_queue\", cnf.DefaultQueue)\n\tassert.Equal(t, \"result_backend\", cnf.ResultBackend)\n\tassert.Equal(t, 123456, cnf.ResultsExpireIn)\n\tassert.Equal(t, \"exchange\", cnf.AMQP.Exchange)\n\tassert.Equal(t, \"exchange_type\", cnf.AMQP.ExchangeType)\n\tassert.Equal(t, \"binding_key\", cnf.AMQP.BindingKey)\n\tassert.Equal(t, \"any\", cnf.AMQP.QueueBindingArgs[\"x-match\"])\n\tassert.Equal(t, \"png\", cnf.AMQP.QueueBindingArgs[\"image-type\"])\n\tassert.Equal(t, 123, cnf.AMQP.PrefetchCount)\n}\n"
  },
  {
    "path": "v1/config/file.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"gopkg.in/yaml.v2\"\n)\n\n// NewFromYaml creates a config object from YAML file\nfunc NewFromYaml(cnfPath string, keepReloading bool) (*Config, error) {\n\tcnf, err := fromFile(cnfPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.INFO.Printf(\"Successfully loaded config from file %s\", cnfPath)\n\n\tif keepReloading {\n\t\t// Open a goroutine to watch remote changes forever\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\t// Delay after each request\n\t\t\t\ttime.Sleep(reloadDelay)\n\n\t\t\t\t// Attempt to reload the config\n\t\t\t\tnewCnf, newErr := fromFile(cnfPath)\n\t\t\t\tif newErr != nil {\n\t\t\t\t\tlog.WARNING.Printf(\"Failed to reload config from file %s: %v\", cnfPath, newErr)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t*cnf = *newCnf\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn cnf, nil\n}\n\n// ReadFromFile reads data from a file\nfunc ReadFromFile(cnfPath string) ([]byte, error) {\n\tfile, err := os.Open(cnfPath)\n\n\t// Config file not found\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Open file error: %s\", err)\n\t}\n\tdefer file.Close()\n\n\t// Config file found, let's try to read it\n\tdata := make([]byte, 1000)\n\tcount, err := file.Read(data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Read from file error: %s\", err)\n\t}\n\n\treturn data[:count], nil\n}\n\nfunc fromFile(cnfPath string) (*Config, error) {\n\tloadedCnf, cnf := new(Config), new(Config)\n\t*cnf = *defaultCnf\n\n\tdata, err := ReadFromFile(cnfPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := yaml.Unmarshal(data, cnf); err != nil {\n\t\treturn nil, fmt.Errorf(\"Unmarshal YAML error: %s\", err)\n\t}\n\tif err := yaml.Unmarshal(data, loadedCnf); err != nil {\n\t\treturn nil, fmt.Errorf(\"Unmarshal YAML error: %s\", err)\n\t}\n\tif loadedCnf.AMQP == nil {\n\t\tcnf.AMQP = nil\n\t}\n\n\treturn cnf, nil\n}\n"
  },
  {
    "path": "v1/config/file_test.go",
    "content": "package config_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar configYAMLData = `---\nbroker: broker\ndefault_queue: default_queue\nresult_backend: result_backend\nresults_expire_in: 123456\namqp:\n  binding_key: binding_key\n  exchange: exchange\n  exchange_type: exchange_type\n  prefetch_count: 123\n  queue_declare_args:\n    x-max-priority: 10\n  queue_binding_args:\n    image-type: png\n    x-match: any\nsqs:\n  receive_wait_time_seconds: 123\n  receive_visibility_timeout: 456\nredis:\n  max_idle: 12\n  max_active: 123\n  max_idle_timeout: 456\n  wait: false\n  read_timeout: 17\n  write_timeout: 19\n  connect_timeout: 21\n  normal_tasks_poll_period: 1001\n  delayed_tasks_poll_period: 23\n  delayed_tasks_key: delayed_tasks_key\n  master_name: master_name\nno_unix_signals: true\ndynamodb:\n  task_states_table: task_states_table\n  group_metas_table: group_metas_table\n`\n\nfunc TestReadFromFile(t *testing.T) {\n\tt.Parallel()\n\n\tdata, err := config.ReadFromFile(\"testconfig.yml\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, configYAMLData, string(data))\n}\n\nfunc TestNewFromYaml(t *testing.T) {\n\tt.Parallel()\n\n\tcnf, err := config.NewFromYaml(\"testconfig.yml\", false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, \"broker\", cnf.Broker)\n\tassert.Equal(t, \"default_queue\", cnf.DefaultQueue)\n\tassert.Equal(t, \"result_backend\", cnf.ResultBackend)\n\tassert.Equal(t, 123456, cnf.ResultsExpireIn)\n\n\tassert.Equal(t, \"exchange\", cnf.AMQP.Exchange)\n\tassert.Equal(t, \"exchange_type\", cnf.AMQP.ExchangeType)\n\tassert.Equal(t, \"binding_key\", cnf.AMQP.BindingKey)\n\tassert.Equal(t, 10, cnf.AMQP.QueueDeclareArgs[\"x-max-priority\"])\n\tassert.Equal(t, \"any\", cnf.AMQP.QueueBindingArgs[\"x-match\"])\n\tassert.Equal(t, \"png\", cnf.AMQP.QueueBindingArgs[\"image-type\"])\n\tassert.Equal(t, 123, cnf.AMQP.PrefetchCount)\n\n\tassert.Equal(t, 123, cnf.SQS.WaitTimeSeconds)\n\tassert.Equal(t, 456, *cnf.SQS.VisibilityTimeout)\n\n\tassert.Equal(t, 12, cnf.Redis.MaxIdle)\n\tassert.Equal(t, 123, cnf.Redis.MaxActive)\n\tassert.Equal(t, 456, cnf.Redis.IdleTimeout)\n\tassert.Equal(t, false, cnf.Redis.Wait)\n\tassert.Equal(t, 17, cnf.Redis.ReadTimeout)\n\tassert.Equal(t, 19, cnf.Redis.WriteTimeout)\n\tassert.Equal(t, 21, cnf.Redis.ConnectTimeout)\n\tassert.Equal(t, 1001, cnf.Redis.NormalTasksPollPeriod)\n\tassert.Equal(t, 23, cnf.Redis.DelayedTasksPollPeriod)\n\tassert.Equal(t, \"delayed_tasks_key\", cnf.Redis.DelayedTasksKey)\n\tassert.Equal(t, \"master_name\", cnf.Redis.MasterName)\n\n\tassert.Equal(t, true, cnf.NoUnixSignals)\n\n\tassert.Equal(t, \"task_states_table\", cnf.DynamoDB.TaskStatesTable)\n\tassert.Equal(t, \"group_metas_table\", cnf.DynamoDB.GroupMetasTable)\n}\n"
  },
  {
    "path": "v1/config/test.env",
    "content": "BROKER=broker\nDEFAULT_QUEUE=default_queue\nRESULT_BACKEND=result_backend\nRESULTS_EXPIRE_IN=123456\nAMQP_BINDING_KEY=binding_key\nAMQP_EXCHANGE=exchange\nAMQP_EXCHANGE_TYPE=exchange_type\nAMQP_PREFETCH_COUNT=123\nAMQP_QUEUE_BINDING_ARGS=image-type:png,x-match:any\n"
  },
  {
    "path": "v1/config/testconfig.yml",
    "content": "---\nbroker: broker\ndefault_queue: default_queue\nresult_backend: result_backend\nresults_expire_in: 123456\namqp:\n  binding_key: binding_key\n  exchange: exchange\n  exchange_type: exchange_type\n  prefetch_count: 123\n  queue_declare_args:\n    x-max-priority: 10\n  queue_binding_args:\n    image-type: png\n    x-match: any\nsqs:\n  receive_wait_time_seconds: 123\n  receive_visibility_timeout: 456\nredis:\n  max_idle: 12\n  max_active: 123\n  max_idle_timeout: 456\n  wait: false\n  read_timeout: 17\n  write_timeout: 19\n  connect_timeout: 21\n  normal_tasks_poll_period: 1001\n  delayed_tasks_poll_period: 23\n  delayed_tasks_key: delayed_tasks_key\n  master_name: master_name\nno_unix_signals: true\ndynamodb:\n  task_states_table: task_states_table\n  group_metas_table: group_metas_table\n"
  },
  {
    "path": "v1/factories.go",
    "content": "package machinery\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\tneturl \"net/url\"\n\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\n\tamqpbroker \"github.com/RichardKnop/machinery/v1/brokers/amqp\"\n\teagerbroker \"github.com/RichardKnop/machinery/v1/brokers/eager\"\n\tgcppubsubbroker \"github.com/RichardKnop/machinery/v1/brokers/gcppubsub\"\n\tbrokeriface \"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\tredisbroker \"github.com/RichardKnop/machinery/v1/brokers/redis\"\n\tsqsbroker \"github.com/RichardKnop/machinery/v1/brokers/sqs\"\n\n\tamqpbackend \"github.com/RichardKnop/machinery/v1/backends/amqp\"\n\tdynamobackend \"github.com/RichardKnop/machinery/v1/backends/dynamodb\"\n\teagerbackend \"github.com/RichardKnop/machinery/v1/backends/eager\"\n\tbackendiface \"github.com/RichardKnop/machinery/v1/backends/iface\"\n\tmemcachebackend \"github.com/RichardKnop/machinery/v1/backends/memcache\"\n\tmongobackend \"github.com/RichardKnop/machinery/v1/backends/mongo\"\n\tnullbackend \"github.com/RichardKnop/machinery/v1/backends/null\"\n\tredisbackend \"github.com/RichardKnop/machinery/v1/backends/redis\"\n\n\teagerlock \"github.com/RichardKnop/machinery/v1/locks/eager\"\n\tlockiface \"github.com/RichardKnop/machinery/v1/locks/iface\"\n\tredislock \"github.com/RichardKnop/machinery/v1/locks/redis\"\n)\n\n// BrokerFactory creates a new object of iface.Broker\n// Currently only AMQP/S broker is supported\nfunc BrokerFactory(cnf *config.Config) (brokeriface.Broker, error) {\n\tif strings.HasPrefix(cnf.Broker, \"amqp://\") {\n\t\treturn amqpbroker.New(cnf), nil\n\t}\n\n\tif strings.HasPrefix(cnf.Broker, \"amqps://\") {\n\t\treturn amqpbroker.New(cnf), nil\n\t}\n\n\tif strings.HasPrefix(cnf.Broker, \"redis://\") || strings.HasPrefix(cnf.Broker, \"rediss://\") {\n\t\tvar scheme string\n\t\tif strings.HasPrefix(cnf.Broker, \"redis://\") {\n\t\t\tscheme = \"redis://\"\n\t\t} else {\n\t\t\tscheme = \"rediss://\"\n\t\t}\n\t\tparts := strings.Split(cnf.Broker, scheme)\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"Redis broker connection string should be in format %shost:port, instead got %s\", scheme,\n\t\t\t\tcnf.Broker,\n\t\t\t)\n\t\t}\n\t\tbrokers := strings.Split(parts[1], \",\")\n\t\tif len(brokers) > 1 || (cnf.Redis != nil && cnf.Redis.ClusterMode) {\n\t\t\tdb, brokers := ParseBrokers(brokers)\n\t\t\treturn redisbroker.NewGR(cnf, brokers, db), nil\n\t\t} else {\n\t\t\tredisHost, redisUsername, redisPassword, redisDB, err := ParseRedisURL(cnf.Broker)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn redisbroker.New(cnf, redisHost, redisUsername, redisPassword, \"\", redisDB), nil\n\t\t}\n\t}\n\n\tif strings.HasPrefix(cnf.Broker, \"redis+socket://\") {\n\t\tredisSocket, redisUsername, redisPassword, redisDB, err := ParseRedisSocketURL(cnf.Broker)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn redisbroker.New(cnf, \"\", redisUsername, redisPassword, redisSocket, redisDB), nil\n\t}\n\n\tif strings.HasPrefix(cnf.Broker, \"eager\") {\n\t\treturn eagerbroker.New(), nil\n\t}\n\n\tif _, ok := os.LookupEnv(\"DISABLE_STRICT_SQS_CHECK\"); ok {\n\t\t//disable SQS name check, so that users can use this with local simulated SQS\n\t\t//where sql broker url might not start with https://sqs\n\n\t\t//even when disabling strict SQS naming check, make sure its still a valid http URL\n\t\tif strings.HasPrefix(cnf.Broker, \"https://\") || strings.HasPrefix(cnf.Broker, \"http://\") {\n\t\t\treturn sqsbroker.New(cnf), nil\n\t\t}\n\t} else {\n\t\tif strings.HasPrefix(cnf.Broker, \"https://sqs\") {\n\t\t\treturn sqsbroker.New(cnf), nil\n\t\t}\n\t}\n\n\tif strings.HasPrefix(cnf.Broker, \"gcppubsub://\") {\n\t\tprojectID, subscriptionName, err := ParseGCPPubSubURL(cnf.Broker)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn gcppubsubbroker.New(cnf, projectID, subscriptionName)\n\t}\n\n\treturn nil, fmt.Errorf(\"Factory failed with broker URL: %v\", cnf.Broker)\n}\n\n// BackendFactory creates a new object of backends.Interface\n// Currently supported backends are AMQP/S and Memcache\nfunc BackendFactory(cnf *config.Config) (backendiface.Backend, error) {\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"amqp://\") {\n\t\treturn amqpbackend.New(cnf), nil\n\t}\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"amqps://\") {\n\t\treturn amqpbackend.New(cnf), nil\n\t}\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"memcache://\") {\n\t\tparts := strings.Split(cnf.ResultBackend, \"memcache://\")\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"Memcache result backend connection string should be in format memcache://server1:port,server2:port, instead got %s\",\n\t\t\t\tcnf.ResultBackend,\n\t\t\t)\n\t\t}\n\t\tservers := strings.Split(parts[1], \",\")\n\t\treturn memcachebackend.New(cnf, servers), nil\n\t}\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"redis://\") || strings.HasPrefix(cnf.ResultBackend, \"rediss://\") {\n\t\tvar scheme string\n\t\tif strings.HasPrefix(cnf.ResultBackend, \"redis://\") {\n\t\t\tscheme = \"redis://\"\n\t\t} else {\n\t\t\tscheme = \"rediss://\"\n\t\t}\n\t\tparts := strings.Split(cnf.ResultBackend, scheme)\n\t\taddrs := strings.Split(parts[1], \",\")\n\t\tif len(addrs) > 1 || (cnf.Redis != nil && cnf.Redis.ClusterMode) {\n\t\t\tdb, addrs := ParseBrokers(addrs)\n\t\t\treturn redisbackend.NewGR(cnf, addrs, db), nil\n\t\t} else {\n\t\t\tredisHost, redisUsername, redisPassword, redisDB, err := ParseRedisURL(cnf.ResultBackend)\n\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn redisbackend.New(cnf, redisHost, redisUsername, redisPassword, \"\", redisDB), nil\n\t\t}\n\t}\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"redis+socket://\") {\n\t\tredisSocket, redisUsername, redisPassword, redisDB, err := ParseRedisSocketURL(cnf.ResultBackend)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn redisbackend.New(cnf, \"\", redisUsername, redisPassword, redisSocket, redisDB), nil\n\t}\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"mongodb://\") ||\n\t\tstrings.HasPrefix(cnf.ResultBackend, \"mongodb+srv://\") {\n\t\treturn mongobackend.New(cnf)\n\t}\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"eager\") {\n\t\treturn eagerbackend.New(), nil\n\t}\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"null\") {\n\t\treturn nullbackend.New(), nil\n\t}\n\n\tif strings.HasPrefix(cnf.ResultBackend, \"https://dynamodb\") {\n\t\treturn dynamobackend.New(cnf), nil\n\t}\n\n\treturn nil, fmt.Errorf(\"Factory failed with result backend: %v\", cnf.ResultBackend)\n}\n\n// ParseRedisURL ...\nfunc ParseRedisURL(url string) (host, username, password string, db int, err error) {\n\t// redis://pwd@host/db\n\n\tvar u *neturl.URL\n\tu, err = neturl.Parse(url)\n\tif err != nil {\n\t\treturn\n\t}\n\tif u.Scheme != \"redis\" && u.Scheme != \"rediss\" {\n\t\terr = errors.New(\"No redis scheme found\")\n\t\treturn\n\t}\n\n\tif u.User != nil {\n\t\tvar exists bool\n\t\tusername = u.User.Username()\n\t\tpassword, exists = u.User.Password()\n\t\tif !exists {\n\t\t\tpassword = u.User.Username()\n\t\t\tusername = \"\"\n\t\t}\n\t}\n\n\thost = u.Host\n\n\tparts := strings.Split(u.Path, \"/\")\n\tif len(parts) == 1 {\n\t\tdb = 0 //default redis db\n\t} else {\n\t\tdb, err = strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\tdb, err = 0, nil //ignore err here\n\t\t}\n\t}\n\n\treturn\n}\n\n// Parse the database index from the last broker in the list, and remove the /db part from it.\nfunc ParseBrokers(brokers []string) (int, []string) {\n\tdb := 0\n\tlast := brokers[len(brokers)-1]\n\tif strings.Contains(last, \"/\") {\n\t\tparts := strings.SplitN(last, \"/\", 2)\n\t\tbrokers[len(brokers)-1] = parts[0]\n\t\tif len(parts) == 2 && parts[1] != \"\" {\n\t\t\tif val, err := strconv.Atoi(parts[1]); err == nil {\n\t\t\t\tdb = val\n\t\t\t}\n\t\t}\n\t}\n\n\treturn db, brokers\n}\n\n// LockFactory creates a new object of iface.Lock\n// Currently supported lock is redis\nfunc LockFactory(cnf *config.Config) (lockiface.Lock, error) {\n\tif strings.HasPrefix(cnf.Lock, \"eager\") {\n\t\treturn eagerlock.New(), nil\n\t}\n\tif strings.HasPrefix(cnf.Lock, \"redis://\") {\n\t\tparts := strings.Split(cnf.Lock, \"redis://\")\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"Redis broker connection string should be in format redis://host:port, instead got %s\",\n\t\t\t\tcnf.Lock,\n\t\t\t)\n\t\t}\n\t\tlocks := strings.Split(parts[1], \",\")\n\t\treturn redislock.New(cnf, locks, 0, 3), nil\n\t}\n\n\t// Lock is required for periodic tasks to work, therefor return in memory lock in case none is configured\n\treturn eagerlock.New(), nil\n}\n\n// ParseRedisSocketURL extracts Redis connection options from a URL with the\n// redis+socket:// scheme. This scheme is not standard (or even de facto) and\n// is used as a transitional mechanism until the the config package gains the\n// proper facilities to support socket-based connections.\nfunc ParseRedisSocketURL(url string) (path, username, password string, db int, err error) {\n\tparts := strings.Split(url, \"redis+socket://\")\n\tif parts[0] != \"\" {\n\t\terr = errors.New(\"No redis scheme found\")\n\t\treturn\n\t}\n\n\t// redis+socket://password@/path/to/file.soc:/db\n\n\tif len(parts) != 2 {\n\t\terr = fmt.Errorf(\"Redis socket connection string should be in format redis+socket://password@/path/to/file.sock:/db, instead got %s\", url)\n\t\treturn\n\t}\n\n\tremainder := parts[1]\n\n\t// Extract password if any\n\tparts = strings.SplitN(remainder, \"@\", 2)\n\tif len(parts) == 2 {\n\t\tuserinfo := strings.SplitN(parts[0], \":\", 2)\n\t\tif len(userinfo) == 2 {\n\t\t\tusername = userinfo[0]\n\t\t\tpassword = userinfo[1]\n\t\t} else {\n\t\t\tpassword = userinfo[0]\n\t\t}\n\t\tremainder = parts[1]\n\t} else {\n\t\tremainder = parts[0]\n\t}\n\n\t// Extract path\n\tparts = strings.SplitN(remainder, \":\", 2)\n\tpath = parts[0]\n\tif path == \"\" {\n\t\terr = fmt.Errorf(\"Redis socket connection string should be in format redis+socket://password@/path/to/file.sock:/db, instead got %s\", url)\n\t\treturn\n\t}\n\tif len(parts) == 2 {\n\t\tremainder = parts[1]\n\t}\n\n\t// Extract DB if any\n\tparts = strings.SplitN(remainder, \"/\", 2)\n\tif len(parts) == 2 {\n\t\tdb, _ = strconv.Atoi(parts[1])\n\t}\n\n\treturn\n}\n\n// ParseGCPPubSubURL Parse GCP Pub/Sub URL\n// url: gcppubsub://YOUR_GCP_PROJECT_ID/YOUR_PUBSUB_SUBSCRIPTION_NAME\nfunc ParseGCPPubSubURL(url string) (string, string, error) {\n\tparts := strings.Split(url, \"gcppubsub://\")\n\tif parts[0] != \"\" {\n\t\treturn \"\", \"\", errors.New(\"No gcppubsub scheme found\")\n\t}\n\n\tif len(parts) != 2 {\n\t\treturn \"\", \"\", fmt.Errorf(\"gcppubsub scheme should be in format gcppubsub://YOUR_GCP_PROJECT_ID/YOUR_PUBSUB_SUBSCRIPTION_NAME, instead got %s\", url)\n\t}\n\n\tremainder := parts[1]\n\n\tparts = strings.Split(remainder, \"/\")\n\tif len(parts) == 2 {\n\t\tif parts[0] == \"\" {\n\t\t\treturn \"\", \"\", fmt.Errorf(\"gcppubsub scheme should be in format gcppubsub://YOUR_GCP_PROJECT_ID/YOUR_PUBSUB_SUBSCRIPTION_NAME, instead got %s\", url)\n\t\t}\n\t\tif parts[1] == \"\" {\n\t\t\treturn \"\", \"\", fmt.Errorf(\"gcppubsub scheme should be in format gcppubsub://YOUR_GCP_PROJECT_ID/YOUR_PUBSUB_SUBSCRIPTION_NAME, instead got %s\", url)\n\t\t}\n\t\treturn parts[0], parts[1], nil\n\t}\n\n\treturn \"\", \"\", fmt.Errorf(\"gcppubsub scheme should be in format gcppubsub://YOUR_GCP_PROJECT_ID/YOUR_PUBSUB_SUBSCRIPTION_NAME, instead got %s\", url)\n}\n"
  },
  {
    "path": "v1/factories_test.go",
    "content": "package machinery_test\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\t\"unsafe\"\n\n\tmachinery \"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/stretchr/testify/assert\"\n\n\tamqpbroker \"github.com/RichardKnop/machinery/v1/brokers/amqp\"\n\tbrokeriface \"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\tredisbroker \"github.com/RichardKnop/machinery/v1/brokers/redis\"\n\tsqsbroker \"github.com/RichardKnop/machinery/v1/brokers/sqs\"\n\n\tamqpbackend \"github.com/RichardKnop/machinery/v1/backends/amqp\"\n\tmemcachebackend \"github.com/RichardKnop/machinery/v1/backends/memcache\"\n\tmongobackend \"github.com/RichardKnop/machinery/v1/backends/mongo\"\n\tredisbackend \"github.com/RichardKnop/machinery/v1/backends/redis\"\n)\n\nvar (\n\tredisSchemeTestCases = []struct {\n\t\tdesc            string\n\t\turl             string\n\t\thost, user, pwd string\n\t\tdb              int\n\t\terr             error\n\t}{\n\t\t{\n\t\t\tdesc: \"invalid redis scheme\",\n\t\t\turl:  \"non_redis://127.0.0.1:5672\",\n\t\t\terr:  errors.New(\"invalid redis scheme\"),\n\t\t},\n\t\t{\n\t\t\tdesc: \"empty redis scheme\",\n\t\t\turl:  \"redis:/\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"redis host\",\n\t\t\turl:  \"redis://127.0.0.1:5672\",\n\t\t\thost: \"127.0.0.1:5672\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"redis password and host\",\n\t\t\turl:  \"redis://pwd@127.0.0.1:5672\",\n\t\t\thost: \"127.0.0.1:5672\",\n\t\t\tuser: \"\",\n\t\t\tpwd:  \"pwd\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"redis password, host and db\",\n\t\t\turl:  \"redis://pwd@127.0.0.1:5672/2\",\n\t\t\thost: \"127.0.0.1:5672\",\n\t\t\tuser: \"\",\n\t\t\tpwd:  \"pwd\",\n\t\t\tdb:   2,\n\t\t},\n\t\t{\n\t\t\tdesc: \"redis user, password host\",\n\t\t\turl:  \"redis://user:pwd@127.0.0.1:5672\",\n\t\t\thost: \"127.0.0.1:5672\",\n\t\t\tuser: \"user\",\n\t\t\tpwd:  \"pwd\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"redis user, password with colon host\",\n\t\t\turl:  \"redis://user:pwd:with:colon@127.0.0.1:5672\",\n\t\t\thost: \"127.0.0.1:5672\",\n\t\t\tuser: \"user\",\n\t\t\tpwd:  \"pwd:with:colon\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"redis user, empty password and host\",\n\t\t\turl:  \"redis://user:@127.0.0.1:5672\",\n\t\t\thost: \"127.0.0.1:5672\",\n\t\t\tuser: \"user\",\n\t\t\tpwd:  \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"redis empty user, password and host\",\n\t\t\turl:  \"redis://:pwd@127.0.0.1:5672\",\n\t\t\thost: \"127.0.0.1:5672\",\n\t\t\tuser: \"\",\n\t\t\tpwd:  \"pwd\",\n\t\t},\n\t}\n)\n\nfunc TestBrokerFactory(t *testing.T) {\n\tt.Parallel()\n\n\tvar cnf config.Config\n\n\t// 1) AMQP broker test\n\n\tcnf = config.Config{\n\t\tBroker:       \"amqp://guest:guest@localhost:5672/\",\n\t\tDefaultQueue: \"machinery_tasks\",\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"machinery_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"machinery_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t}\n\n\tactual, err := machinery.BrokerFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\t_, isAMQPBroker := actual.(*amqpbroker.Broker)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tisAMQPBroker,\n\t\t\t\"Broker should be instance of *brokers.AMQPBroker\",\n\t\t)\n\t\texpected := amqpbroker.New(&cnf)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tbrokerEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// 2) Redis broker test\n\n\t// with password\n\tcnf = config.Config{\n\t\tBroker:       \"redis://password@localhost:6379\",\n\t\tDefaultQueue: \"machinery_tasks\",\n\t}\n\n\tactual, err = machinery.BrokerFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\t_, isRedisBroker := actual.(*redisbroker.Broker)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tisRedisBroker,\n\t\t\t\"Broker should be instance of *brokers.RedisBroker\",\n\t\t)\n\t\texpected := redisbroker.New(&cnf, \"localhost:6379\", \"\", \"password\", \"\", 0)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tbrokerEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// without password\n\tcnf = config.Config{\n\t\tBroker:       \"redis://localhost:6379\",\n\t\tDefaultQueue: \"machinery_tasks\",\n\t}\n\n\tactual, err = machinery.BrokerFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\t_, isRedisBroker := actual.(*redisbroker.Broker)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tisRedisBroker,\n\t\t\t\"Broker should be instance of *brokers.RedisBroker\",\n\t\t)\n\t\texpected := redisbroker.New(&cnf, \"localhost:6379\", \"\", \"\", \"\", 0)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tbrokerEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// using a socket file\n\tcnf = config.Config{\n\t\tBroker:       \"redis+socket:///tmp/redis.sock\",\n\t\tDefaultQueue: \"machinery_tasks\",\n\t}\n\n\tactual, err = machinery.BrokerFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\t_, isRedisBroker := actual.(*redisbroker.Broker)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tisRedisBroker,\n\t\t\t\"Broker should be instance of *brokers.RedisBroker\",\n\t\t)\n\t\texpected := redisbroker.New(&cnf, \"\", \"\", \"\", \"/tmp/redis.sock\", 0)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tbrokerEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// 3) AWS SQS\n\tcnf = config.Config{\n\t\tBroker:       \"https://sqs.us-east-2.amazonaws.com/123456789012\",\n\t\tDefaultQueue: \"machinery_tasks\",\n\t}\n\n\tactual, err = machinery.BrokerFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\t_, isAWSSQSBroker := actual.(*sqsbroker.Broker)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tisAWSSQSBroker,\n\t\t\t\"Broker should be instance of *brokers.AWSSQSBroker\",\n\t\t)\n\t}\n\n\t// 4) local SQS config should pass with special env variable\n\t// AWS SQS Invalid SQS Check\n\tcnf = config.Config{\n\t\tBroker:       \"http://localhost:5672/some-queue\",\n\t\tDefaultQueue: \"machinery_tasks\",\n\t}\n\n\tos.Setenv(\"DISABLE_STRICT_SQS_CHECK\", \"yes\")\n\tactual, err = machinery.BrokerFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\t_, isAWSSQSBroker := actual.(*sqsbroker.Broker)\n\t\tassert.True(\n\t\t\tt,\n\t\t\tisAWSSQSBroker,\n\t\t\t\"Broker should be instance of *brokers.AWSSQSBroker\",\n\t\t)\n\t}\n\tos.Unsetenv(\"DISABLE_STRICT_SQS_CHECK\")\n\n}\n\nfunc brokerEqual(x, y brokeriface.Broker) bool {\n\t// unset Broker.stopChan and Broker.retryStopChan to nil before using\n\t// reflect.DeepEqual() as the objects will have a different address\n\trx := reflect.ValueOf(x).Elem()\n\trxf := rx.FieldByName(\"stopChan\")\n\trxf = reflect.NewAt(rxf.Type(), unsafe.Pointer(rxf.UnsafeAddr())).Elem()\n\trxf.Set(reflect.Zero(rxf.Type()))\n\trxf = rx.FieldByName(\"retryStopChan\")\n\trxf = reflect.NewAt(rxf.Type(), unsafe.Pointer(rxf.UnsafeAddr())).Elem()\n\trxf.Set(reflect.Zero(rxf.Type()))\n\n\try := reflect.ValueOf(y).Elem()\n\tryf := ry.FieldByName(\"stopChan\")\n\tryf = reflect.NewAt(ryf.Type(), unsafe.Pointer(ryf.UnsafeAddr())).Elem()\n\tryf.Set(reflect.Zero(ryf.Type()))\n\tryf = ry.FieldByName(\"retryStopChan\")\n\tryf = reflect.NewAt(ryf.Type(), unsafe.Pointer(ryf.UnsafeAddr())).Elem()\n\tryf.Set(reflect.Zero(ryf.Type()))\n\n\treturn reflect.DeepEqual(x, y)\n}\n\nfunc TestBrokerFactoryError(t *testing.T) {\n\tt.Parallel()\n\n\tcnf := config.Config{\n\t\tBroker: \"BOGUS\",\n\t}\n\n\tconn, err := machinery.BrokerFactory(&cnf)\n\tif assert.Error(t, err) {\n\t\tassert.Nil(t, conn)\n\t\tassert.Equal(t, \"Factory failed with broker URL: BOGUS\", err.Error())\n\t}\n\n\t// AWS SQS Invalid SQS Check\n\tcnf = config.Config{\n\t\tBroker:       \"http://localhost:5672/some-queue\",\n\t\tDefaultQueue: \"machinery_tasks\",\n\t}\n\n\tconn, err = machinery.BrokerFactory(&cnf)\n\tif assert.Error(t, err) {\n\t\tassert.Nil(t, conn)\n\t\tassert.Equal(t, \"Factory failed with broker URL: http://localhost:5672/some-queue\", err.Error())\n\t}\n\n\t// Non-AWS SQS URL allowed but not invalid http ones\n\tos.Setenv(\"DISABLE_STRICT_SQS_CHECK\", \"yes\")\n\tcnf = config.Config{\n\t\tBroker:       \"localhost:5672/some-queue\",\n\t\tDefaultQueue: \"machinery_tasks\",\n\t}\n\n\tconn, err = machinery.BrokerFactory(&cnf)\n\tif assert.Error(t, err) {\n\t\tassert.Nil(t, conn)\n\t\tassert.Equal(t, \"Factory failed with broker URL: localhost:5672/some-queue\", err.Error())\n\t}\n\tos.Unsetenv(\"DISABLE_STRICT_SQS_CHECK\")\n}\n\nfunc TestBackendFactory(t *testing.T) {\n\tt.Parallel()\n\n\tvar cnf config.Config\n\n\t// 1) AMQP backend test\n\n\tcnf = config.Config{ResultBackend: \"amqp://guest:guest@localhost:5672/\"}\n\n\tactual, err := machinery.BackendFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\texpected := amqpbackend.New(&cnf)\n\t\tassert.True(\n\t\t\tt,\n\t\t\treflect.DeepEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// 2) Memcache backend test\n\n\tcnf = config.Config{\n\t\tResultBackend: \"memcache://10.0.0.1:11211,10.0.0.2:11211\",\n\t}\n\n\tactual, err = machinery.BackendFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\tservers := []string{\"10.0.0.1:11211\", \"10.0.0.2:11211\"}\n\t\texpected := memcachebackend.New(&cnf, servers)\n\t\tassert.True(\n\t\t\tt,\n\t\t\treflect.DeepEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// 2) Redis backend test\n\n\t// with password\n\tcnf = config.Config{\n\t\tResultBackend: \"redis://password@localhost:6379\",\n\t}\n\n\tactual, err = machinery.BackendFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\texpected := redisbackend.New(&cnf, \"localhost:6379\", \"\", \"password\", \"\", 0)\n\t\tassert.True(\n\t\t\tt,\n\t\t\treflect.DeepEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// without password\n\tcnf = config.Config{\n\t\tResultBackend: \"redis://localhost:6379\",\n\t}\n\n\tactual, err = machinery.BackendFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\texpected := redisbackend.New(&cnf, \"localhost:6379\", \"\", \"\", \"\", 0)\n\t\tassert.True(\n\t\t\tt,\n\t\t\treflect.DeepEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// using a socket file\n\tcnf = config.Config{\n\t\tResultBackend: \"redis+socket:///tmp/redis.sock\",\n\t}\n\n\tactual, err = machinery.BackendFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\texpected := redisbackend.New(&cnf, \"\", \"\", \"\", \"/tmp/redis.sock\", 0)\n\t\tassert.True(\n\t\t\tt,\n\t\t\treflect.DeepEqual(actual, expected),\n\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t)\n\t}\n\n\t// 4) MongoDB backend test\n\tcnf = config.Config{\n\t\tResultBackend:   \"mongodb://mongo:27017\",\n\t\tResultsExpireIn: 30,\n\t}\n\n\tactual, err = machinery.BackendFactory(&cnf)\n\tif assert.NoError(t, err) {\n\t\texpected, err := mongobackend.New(&cnf)\n\t\tif assert.NoError(t, err) {\n\t\t\tassert.True(\n\t\t\t\tt,\n\t\t\t\treflect.DeepEqual(actual, expected),\n\t\t\t\tfmt.Sprintf(\"conn = %v, want %v\", actual, expected),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestBackendFactoryError(t *testing.T) {\n\tt.Parallel()\n\n\tcnf := config.Config{\n\t\tResultBackend: \"BOGUS\",\n\t}\n\n\tconn, err := machinery.BackendFactory(&cnf)\n\tif assert.Error(t, err) {\n\t\tassert.Nil(t, conn)\n\t\tassert.Equal(t, \"Factory failed with result backend: BOGUS\", err.Error())\n\t}\n\n\tif conn != nil {\n\t\tt.Errorf(\"conn = %v, should be nil\", conn)\n\t}\n}\n\nfunc TestParseRedisURL(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, tc := range redisSchemeTestCases {\n\t\ttc := tc // capture range variable\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\thost, user, pwd, db, err := machinery.ParseRedisURL(tc.url)\n\t\t\tif tc.err != nil {\n\t\t\t\tassert.Error(t, err, tc.err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif assert.NoError(t, err) {\n\t\t\t\tassert.Equal(t, tc.host, host)\n\t\t\t\tassert.Equal(t, tc.user, user)\n\t\t\t\tassert.Equal(t, tc.pwd, pwd)\n\t\t\t\tassert.Equal(t, tc.db, db)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseRedisSocketURL(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tpath, user, pwd, url string\n\t\tdb                   int\n\t\terr                  error\n\t)\n\n\turl = \"non_redissock:///tmp/redis.sock\"\n\t_, _, _, _, err = machinery.ParseRedisSocketURL(url)\n\tassert.Error(t, err, \"invalid redis scheme\")\n\n\turl = \"redis+socket:/\"\n\t_, _, _, _, err = machinery.ParseRedisSocketURL(url)\n\tassert.Error(t, err, \"invalid redis url scheme\")\n\n\turl = \"redis+socket:///tmp/redis.sock\"\n\tpath, user, pwd, db, err = machinery.ParseRedisSocketURL(url)\n\tif assert.NoError(t, err) {\n\t\tassert.Equal(t, \"/tmp/redis.sock\", path)\n\t\tassert.Equal(t, \"\", user)\n\t\tassert.Equal(t, \"\", pwd)\n\t\tassert.Equal(t, 0, db)\n\t}\n\n\turl = \"redis+socket://user:pwd@/tmp/redis.sock\"\n\tpath, user, pwd, db, _ = machinery.ParseRedisSocketURL(url)\n\tif assert.NoError(t, err) {\n\t\tassert.Equal(t, \"/tmp/redis.sock\", path)\n\t\tassert.Equal(t, \"user\", user)\n\t\tassert.Equal(t, \"pwd\", pwd)\n\t\tassert.Equal(t, 0, db)\n\t}\n\n\turl = \"redis+socket://user:pwd@/tmp/redis.sock:/2\"\n\tpath, user, pwd, db, err = machinery.ParseRedisSocketURL(url)\n\tif assert.NoError(t, err) {\n\t\tassert.Equal(t, \"/tmp/redis.sock\", path)\n\t\tassert.Equal(t, \"user\", user)\n\t\tassert.Equal(t, \"pwd\", pwd)\n\t\tassert.Equal(t, 2, db)\n\t}\n}\n"
  },
  {
    "path": "v1/locks/eager/eager.go",
    "content": "package eager\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\tErrEagerLockFailed = errors.New(\"eager lock: failed to acquire lock\")\n)\n\ntype Lock struct {\n\tretries  int\n\tinterval time.Duration\n\tregister struct {\n\t\tsync.RWMutex\n\t\tm map[string]int64\n\t}\n}\n\nfunc New() *Lock {\n\treturn &Lock{\n\t\tretries:  3,\n\t\tinterval: 5 * time.Second,\n\t\tregister: struct {\n\t\t\tsync.RWMutex\n\t\t\tm map[string]int64\n\t\t}{\n\t\t\tm: make(map[string]int64),\n\t\t},\n\t}\n}\n\nfunc (e *Lock) LockWithRetries(key string, value int64) error {\n\tfor i := 0; i <= e.retries; i++ {\n\t\terr := e.Lock(key, value)\n\t\tif err == nil {\n\t\t\t//成功拿到锁，返回\n\t\t\treturn nil\n\t\t}\n\n\t\ttime.Sleep(e.interval)\n\t}\n\treturn ErrEagerLockFailed\n}\n\nfunc (e *Lock) Lock(key string, value int64) error {\n\te.register.Lock()\n\tdefer e.register.Unlock()\n\ttimeout, exist := e.register.m[key]\n\tif !exist || time.Now().UnixNano() > timeout {\n\t\te.register.m[key] = value\n\t\treturn nil\n\t}\n\treturn ErrEagerLockFailed\n}\n"
  },
  {
    "path": "v1/locks/eager/eager_test.go",
    "content": "package eager\n\nimport (\n\tlockiface \"github.com/RichardKnop/machinery/v1/locks/iface\"\n\t\"github.com/RichardKnop/machinery/v1/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestLock_Lock(t *testing.T) {\n\tlock := New()\n\tkeyName := utils.GetPureUUID()\n\n\tgo func() {\n\t\terr := lock.Lock(keyName, time.Now().Add(25*time.Second).UnixNano())\n\t\tassert.NoError(t, err)\n\t}()\n\ttime.Sleep(1 * time.Second)\n\terr := lock.Lock(keyName, time.Now().Add(25*time.Second).UnixNano())\n\tassert.Error(t, err)\n\tassert.EqualError(t, err, ErrEagerLockFailed.Error())\n}\n\nfunc TestLock_LockWithRetries(t *testing.T) {\n\tlock := New()\n\tkeyName := utils.GetPureUUID()\n\n\tgo func() {\n\t\terr := lock.LockWithRetries(keyName, time.Now().Add(25*time.Second).UnixNano())\n\t\tassert.NoError(t, err)\n\t}()\n\ttime.Sleep(1 * time.Second)\n\terr := lock.LockWithRetries(keyName, time.Now().Add(25*time.Second).UnixNano())\n\tassert.Error(t, err)\n\tassert.EqualError(t, err, ErrEagerLockFailed.Error())\n}\n\nfunc TestNew(t *testing.T) {\n\tlock := New()\n\tassert.Implements(t, (*lockiface.Lock)(nil), lock)\n}\n"
  },
  {
    "path": "v1/locks/iface/interfaces.go",
    "content": "package iface\n\ntype Lock interface {\n\t//Acquire the lock with retry\n\t//key: the name of the lock,\n\t//value: at the nanosecond timestamp that lock needs to be released automatically\n\tLockWithRetries(key string, value int64) error\n\n\t//Acquire the lock with once\n\t//key: the name of the lock,\n\t//value: at the nanosecond timestamp that lock needs to be released automatically\n\tLock(key string, value int64) error\n}\n"
  },
  {
    "path": "v1/locks/redis/redis.go",
    "content": "package redis\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/redis/go-redis/v9\"\n)\n\nvar (\n\tErrRedisLockFailed = errors.New(\"redis lock: failed to acquire lock\")\n)\n\ntype Lock struct {\n\trclient  redis.UniversalClient\n\tretries  int\n\tinterval time.Duration\n}\n\nfunc New(cnf *config.Config, addrs []string, db, retries int) Lock {\n\tif retries <= 0 {\n\t\treturn Lock{}\n\t}\n\tlock := Lock{retries: retries}\n\n\tvar password string\n\n\tparts := strings.Split(addrs[0], \"@\")\n\tif len(parts) >= 2 {\n\t\tpassword = strings.Join(parts[:len(parts)-1], \"@\")\n\t\taddrs[0] = parts[len(parts)-1] // addr is the last one without @\n\t}\n\n\tropt := &redis.UniversalOptions{\n\t\tAddrs:    addrs,\n\t\tDB:       db,\n\t\tPassword: password,\n\t}\n\tif cnf.Redis != nil {\n\t\tropt.MasterName = cnf.Redis.MasterName\n\t}\n\n\tlock.rclient = redis.NewUniversalClient(ropt)\n\n\treturn lock\n}\n\nfunc (r Lock) LockWithRetries(key string, unixTsToExpireNs int64) error {\n\tfor i := 0; i <= r.retries; i++ {\n\t\terr := r.Lock(key, unixTsToExpireNs)\n\t\tif err == nil {\n\t\t\t//成功拿到锁，返回\n\t\t\treturn nil\n\t\t}\n\n\t\ttime.Sleep(r.interval)\n\t}\n\treturn ErrRedisLockFailed\n}\n\nfunc (r Lock) Lock(key string, unixTsToExpireNs int64) error {\n\tnow := time.Now().UnixNano()\n\texpiration := time.Duration(unixTsToExpireNs + 1 - now)\n\t// ctx := r.rclient.Context()\n\tctx := context.Background()\n\n\tsuccess, err := r.rclient.SetNX(ctx, key, unixTsToExpireNs, expiration).Result()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !success {\n\t\tv, err := r.rclient.Get(ctx, key).Result()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttimeout, err := strconv.Atoi(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif timeout != 0 && now > int64(timeout) {\n\t\t\tnewTimeout, err := r.rclient.GetSet(ctx, key, unixTsToExpireNs).Result()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcurTimeout, err := strconv.Atoi(newTimeout)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif now > int64(curTimeout) {\n\t\t\t\t// success to acquire lock with get set\n\t\t\t\t// set the expiration of redis key\n\t\t\t\tr.rclient.Expire(ctx, key, expiration)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn ErrRedisLockFailed\n\t\t}\n\n\t\treturn ErrRedisLockFailed\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "v1/log/log.go",
    "content": "package log\n\nimport (\n\t\"github.com/RichardKnop/logging\"\n)\n\nvar (\n\tlogger = logging.New(nil, nil, new(logging.ColouredFormatter))\n\n\t// DEBUG ...\n\tDEBUG = logger[logging.DEBUG]\n\t// INFO ...\n\tINFO = logger[logging.INFO]\n\t// WARNING ...\n\tWARNING = logger[logging.WARNING]\n\t// ERROR ...\n\tERROR = logger[logging.ERROR]\n\t// FATAL ...\n\tFATAL = logger[logging.FATAL]\n)\n\n// Set sets a custom logger for all log levels\nfunc Set(l logging.LoggerInterface) {\n\tDEBUG = l\n\tINFO = l\n\tWARNING = l\n\tERROR = l\n\tFATAL = l\n}\n\n// SetDebug sets a custom logger for DEBUG level logs\nfunc SetDebug(l logging.LoggerInterface) {\n\tDEBUG = l\n}\n\n// SetInfo sets a custom logger for INFO level logs\nfunc SetInfo(l logging.LoggerInterface) {\n\tINFO = l\n}\n\n// SetWarning sets a custom logger for WARNING level logs\nfunc SetWarning(l logging.LoggerInterface) {\n\tWARNING = l\n}\n\n// SetError sets a custom logger for ERROR level logs\nfunc SetError(l logging.LoggerInterface) {\n\tERROR = l\n}\n\n// SetFatal sets a custom logger for FATAL level logs\nfunc SetFatal(l logging.LoggerInterface) {\n\tFATAL = l\n}\n"
  },
  {
    "path": "v1/log/log_test.go",
    "content": "package log_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/log\"\n)\n\nfunc TestDefaultLogger(t *testing.T) {\n\tlog.INFO.Print(\"should not panic\")\n\tlog.WARNING.Print(\"should not panic\")\n\tlog.ERROR.Print(\"should not panic\")\n\tlog.FATAL.Print(\"should not panic\")\n}\n"
  },
  {
    "path": "v1/package.go",
    "content": "package machinery\n"
  },
  {
    "path": "v1/retry/fibonacci.go",
    "content": "package retry\n\n// Fibonacci returns successive Fibonacci numbers starting from 1\nfunc Fibonacci() func() int {\n\ta, b := 0, 1\n\treturn func() int {\n\t\ta, b = b, a+b\n\t\treturn a\n\t}\n}\n\n// FibonacciNext returns next number in Fibonacci sequence greater than start\nfunc FibonacciNext(start int) int {\n\tfib := Fibonacci()\n\tnum := fib()\n\tfor num <= start {\n\t\tnum = fib()\n\t}\n\treturn num\n}\n"
  },
  {
    "path": "v1/retry/fibonacci_test.go",
    "content": "package retry_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/retry\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestFibonacci(t *testing.T) {\n\tfibonacci := retry.Fibonacci()\n\n\tsequence := []int{\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t}\n\n\tassert.EqualValues(t, sequence, []int{1, 1, 2, 3, 5, 8})\n}\n\nfunc TestFibonacciNext(t *testing.T) {\n\tassert.Equal(t, 1, retry.FibonacciNext(0))\n\tassert.Equal(t, 2, retry.FibonacciNext(1))\n\tassert.Equal(t, 5, retry.FibonacciNext(3))\n\tassert.Equal(t, 5, retry.FibonacciNext(4))\n\tassert.Equal(t, 8, retry.FibonacciNext(5))\n\tassert.Equal(t, 13, retry.FibonacciNext(8))\n}\n"
  },
  {
    "path": "v1/retry/retry.go",
    "content": "package retry\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/log\"\n)\n\n// Closure - a useful closure we can use when there is a problem\n// connecting to the broker. It uses Fibonacci sequence to space out retry attempts\nvar Closure = func() func(chan int) {\n\tretryIn := 0\n\tfibonacci := Fibonacci()\n\treturn func(stopChan chan int) {\n\t\tif retryIn > 0 {\n\t\t\tdurationString := fmt.Sprintf(\"%vs\", retryIn)\n\t\t\tduration, _ := time.ParseDuration(durationString)\n\n\t\t\tlog.WARNING.Printf(\"Retrying in %v seconds\", retryIn)\n\n\t\t\tselect {\n\t\t\tcase <-stopChan:\n\t\t\t\tbreak\n\t\t\tcase <-time.After(duration):\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tretryIn = fibonacci()\n\t}\n}\n"
  },
  {
    "path": "v1/server.go",
    "content": "package machinery\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/robfig/cron/v3\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/result\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/eager\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/RichardKnop/machinery/v1/tracing\"\n\t\"github.com/RichardKnop/machinery/v1/utils\"\n\n\tbackendsiface \"github.com/RichardKnop/machinery/v1/backends/iface\"\n\tbrokersiface \"github.com/RichardKnop/machinery/v1/brokers/iface\"\n\tlockiface \"github.com/RichardKnop/machinery/v1/locks/iface\"\n\topentracing \"github.com/opentracing/opentracing-go\"\n)\n\n// Server is the main Machinery object and stores all configuration\n// All the tasks workers process are registered against the server\ntype Server struct {\n\tconfig            *config.Config\n\tregisteredTasks   *sync.Map\n\tbroker            brokersiface.Broker\n\tbackend           backendsiface.Backend\n\tlock              lockiface.Lock\n\tscheduler         *cron.Cron\n\tprePublishHandler func(*tasks.Signature)\n}\n\n// NewServerWithBrokerBackend ...\nfunc NewServerWithBrokerBackendLock(cnf *config.Config, brokerServer brokersiface.Broker, backendServer backendsiface.Backend, lock lockiface.Lock) *Server {\n\tsrv := &Server{\n\t\tconfig:          cnf,\n\t\tregisteredTasks: new(sync.Map),\n\t\tbroker:          brokerServer,\n\t\tbackend:         backendServer,\n\t\tlock:            lock,\n\t\tscheduler:       cron.New(),\n\t}\n\n\t// Run scheduler job\n\tgo srv.scheduler.Run()\n\n\treturn srv\n}\n\n// NewServer creates Server instance\nfunc NewServer(cnf *config.Config) (*Server, error) {\n\tbroker, err := BrokerFactory(cnf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Backend is optional so we ignore the error\n\tbackend, _ := BackendFactory(cnf)\n\n\t// Init lock\n\tlock, err := LockFactory(cnf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsrv := NewServerWithBrokerBackendLock(cnf, broker, backend, lock)\n\n\t// init for eager-mode\n\teager, ok := broker.(eager.Mode)\n\tif ok {\n\t\t// we don't have to call worker.Launch in eager mode\n\t\teager.AssignWorker(srv.NewWorker(\"eager\", 0))\n\t}\n\n\treturn srv, nil\n}\n\n// NewWorker creates Worker instance\nfunc (server *Server) NewWorker(consumerTag string, concurrency int) *Worker {\n\treturn &Worker{\n\t\tserver:      server,\n\t\tConsumerTag: consumerTag,\n\t\tConcurrency: concurrency,\n\t\tQueue:       \"\",\n\t}\n}\n\n// NewCustomQueueWorker creates Worker instance with Custom Queue\nfunc (server *Server) NewCustomQueueWorker(consumerTag string, concurrency int, queue string) *Worker {\n\treturn &Worker{\n\t\tserver:      server,\n\t\tConsumerTag: consumerTag,\n\t\tConcurrency: concurrency,\n\t\tQueue:       queue,\n\t}\n}\n\n// GetBroker returns broker\nfunc (server *Server) GetBroker() brokersiface.Broker {\n\treturn server.broker\n}\n\n// SetBroker sets broker\nfunc (server *Server) SetBroker(broker brokersiface.Broker) {\n\tserver.broker = broker\n}\n\n// GetBackend returns backend\nfunc (server *Server) GetBackend() backendsiface.Backend {\n\treturn server.backend\n}\n\n// SetBackend sets backend\nfunc (server *Server) SetBackend(backend backendsiface.Backend) {\n\tserver.backend = backend\n}\n\n// GetConfig returns connection object\nfunc (server *Server) GetConfig() *config.Config {\n\treturn server.config\n}\n\n// SetConfig sets config\nfunc (server *Server) SetConfig(cnf *config.Config) {\n\tserver.config = cnf\n}\n\n// SetPreTaskHandler Sets pre publish handler\nfunc (server *Server) SetPreTaskHandler(handler func(*tasks.Signature)) {\n\tserver.prePublishHandler = handler\n}\n\n// RegisterTasks registers all tasks at once\nfunc (server *Server) RegisterTasks(namedTaskFuncs map[string]interface{}) error {\n\tfor _, task := range namedTaskFuncs {\n\t\tif err := tasks.ValidateTask(task); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor k, v := range namedTaskFuncs {\n\t\tserver.registeredTasks.Store(k, v)\n\t}\n\n\tserver.broker.SetRegisteredTaskNames(server.GetRegisteredTaskNames())\n\treturn nil\n}\n\n// RegisterTask registers a single task\nfunc (server *Server) RegisterTask(name string, taskFunc interface{}) error {\n\tif err := tasks.ValidateTask(taskFunc); err != nil {\n\t\treturn err\n\t}\n\tserver.registeredTasks.Store(name, taskFunc)\n\tserver.broker.SetRegisteredTaskNames(server.GetRegisteredTaskNames())\n\treturn nil\n}\n\n// IsTaskRegistered returns true if the task name is registered with this broker\nfunc (server *Server) IsTaskRegistered(name string) bool {\n\t_, ok := server.registeredTasks.Load(name)\n\treturn ok\n}\n\n// GetRegisteredTask returns registered task by name\nfunc (server *Server) GetRegisteredTask(name string) (interface{}, error) {\n\ttaskFunc, ok := server.registeredTasks.Load(name)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"Task not registered error: %s\", name)\n\t}\n\treturn taskFunc, nil\n}\n\n// SendTaskWithContext will inject the trace context in the signature headers before publishing it\nfunc (server *Server) SendTaskWithContext(ctx context.Context, signature *tasks.Signature) (*result.AsyncResult, error) {\n\tspan, _ := opentracing.StartSpanFromContext(ctx, \"SendTask\", tracing.ProducerOption(), tracing.MachineryTag)\n\tdefer span.Finish()\n\n\t// tag the span with some info about the signature\n\tsignature.Headers = tracing.HeadersWithSpan(signature.Headers, span)\n\n\t// Make sure result backend is defined\n\tif server.backend == nil {\n\t\treturn nil, errors.New(\"Result backend required\")\n\t}\n\n\t// Auto generate a UUID if not set already\n\tif signature.UUID == \"\" {\n\t\ttaskID := uuid.New().String()\n\t\tsignature.UUID = fmt.Sprintf(\"task_%v\", taskID)\n\t}\n\n\t// Set initial task state to PENDING\n\tif err := server.backend.SetStatePending(signature); err != nil {\n\t\treturn nil, fmt.Errorf(\"Set state pending error: %s\", err)\n\t}\n\n\tif server.prePublishHandler != nil {\n\t\tserver.prePublishHandler(signature)\n\t}\n\n\tif err := server.broker.Publish(ctx, signature); err != nil {\n\t\treturn nil, fmt.Errorf(\"Publish message error: %s\", err)\n\t}\n\n\treturn result.NewAsyncResult(signature, server.backend), nil\n}\n\n// SendTask publishes a task to the default queue\nfunc (server *Server) SendTask(signature *tasks.Signature) (*result.AsyncResult, error) {\n\treturn server.SendTaskWithContext(context.Background(), signature)\n}\n\n// SendChainWithContext will inject the trace context in all the signature headers before publishing it\nfunc (server *Server) SendChainWithContext(ctx context.Context, chain *tasks.Chain) (*result.ChainAsyncResult, error) {\n\tspan, _ := opentracing.StartSpanFromContext(ctx, \"SendChain\", tracing.ProducerOption(), tracing.MachineryTag, tracing.WorkflowChainTag)\n\tdefer span.Finish()\n\n\ttracing.AnnotateSpanWithChainInfo(span, chain)\n\n\treturn server.SendChain(chain)\n}\n\n// SendChain triggers a chain of tasks\nfunc (server *Server) SendChain(chain *tasks.Chain) (*result.ChainAsyncResult, error) {\n\t_, err := server.SendTask(chain.Tasks[0])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result.NewChainAsyncResult(chain.Tasks, server.backend), nil\n}\n\n// SendGroupWithContext will inject the trace context in all the signature headers before publishing it\nfunc (server *Server) SendGroupWithContext(ctx context.Context, group *tasks.Group, sendConcurrency int) ([]*result.AsyncResult, error) {\n\tspan, _ := opentracing.StartSpanFromContext(ctx, \"SendGroup\", tracing.ProducerOption(), tracing.MachineryTag, tracing.WorkflowGroupTag)\n\tdefer span.Finish()\n\n\ttracing.AnnotateSpanWithGroupInfo(span, group, sendConcurrency)\n\n\t// Make sure result backend is defined\n\tif server.backend == nil {\n\t\treturn nil, errors.New(\"Result backend required\")\n\t}\n\n\tasyncResults := make([]*result.AsyncResult, len(group.Tasks))\n\n\tvar wg sync.WaitGroup\n\twg.Add(len(group.Tasks))\n\terrorsChan := make(chan error, len(group.Tasks)*2)\n\n\t// Init group\n\tserver.backend.InitGroup(group.GroupUUID, group.GetUUIDs())\n\n\t// Init the tasks Pending state first\n\tfor _, signature := range group.Tasks {\n\t\tif err := server.backend.SetStatePending(signature); err != nil {\n\t\t\terrorsChan <- err\n\t\t\tcontinue\n\t\t}\n\t}\n\n\tpool := make(chan struct{}, sendConcurrency)\n\tgo func() {\n\t\tfor i := 0; i < sendConcurrency; i++ {\n\t\t\tpool <- struct{}{}\n\t\t}\n\t}()\n\n\tfor i, signature := range group.Tasks {\n\n\t\tif sendConcurrency > 0 {\n\t\t\t<-pool\n\t\t}\n\n\t\tgo func(s *tasks.Signature, index int) {\n\t\t\tdefer wg.Done()\n\n\t\t\t// Publish task\n\n\t\t\terr := server.broker.Publish(ctx, s)\n\n\t\t\tif sendConcurrency > 0 {\n\t\t\t\tpool <- struct{}{}\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\terrorsChan <- fmt.Errorf(\"Publish message error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tasyncResults[index] = result.NewAsyncResult(s, server.backend)\n\t\t}(signature, i)\n\t}\n\n\tdone := make(chan int)\n\tgo func() {\n\t\twg.Wait()\n\t\tdone <- 1\n\t}()\n\n\tselect {\n\tcase err := <-errorsChan:\n\t\treturn asyncResults, err\n\tcase <-done:\n\t\treturn asyncResults, nil\n\t}\n}\n\n// SendGroup triggers a group of parallel tasks\nfunc (server *Server) SendGroup(group *tasks.Group, sendConcurrency int) ([]*result.AsyncResult, error) {\n\treturn server.SendGroupWithContext(context.Background(), group, sendConcurrency)\n}\n\n// SendChordWithContext will inject the trace context in all the signature headers before publishing it\nfunc (server *Server) SendChordWithContext(ctx context.Context, chord *tasks.Chord, sendConcurrency int) (*result.ChordAsyncResult, error) {\n\tspan, _ := opentracing.StartSpanFromContext(ctx, \"SendChord\", tracing.ProducerOption(), tracing.MachineryTag, tracing.WorkflowChordTag)\n\tdefer span.Finish()\n\n\ttracing.AnnotateSpanWithChordInfo(span, chord, sendConcurrency)\n\n\t_, err := server.SendGroupWithContext(ctx, chord.Group, sendConcurrency)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result.NewChordAsyncResult(\n\t\tchord.Group.Tasks,\n\t\tchord.Callback,\n\t\tserver.backend,\n\t), nil\n}\n\n// SendChord triggers a group of parallel tasks with a callback\nfunc (server *Server) SendChord(chord *tasks.Chord, sendConcurrency int) (*result.ChordAsyncResult, error) {\n\treturn server.SendChordWithContext(context.Background(), chord, sendConcurrency)\n}\n\n// GetRegisteredTaskNames returns slice of registered task names\nfunc (server *Server) GetRegisteredTaskNames() []string {\n\ttaskNames := make([]string, 0)\n\n\tserver.registeredTasks.Range(func(key, value interface{}) bool {\n\t\ttaskNames = append(taskNames, key.(string))\n\t\treturn true\n\t})\n\treturn taskNames\n}\n\n// RegisterPeriodicTask register a periodic task which will be triggered periodically\nfunc (server *Server) RegisterPeriodicTask(spec, name string, signature *tasks.Signature) error {\n\t//check spec\n\tschedule, err := cron.ParseStandard(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf := func() {\n\t\t//get lock\n\t\terr := server.lock.LockWithRetries(utils.GetLockName(name, spec), schedule.Next(time.Now()).UnixNano()-1)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t//send task\n\t\t_, err = server.SendTask(tasks.CopySignature(signature))\n\t\tif err != nil {\n\t\t\tlog.ERROR.Printf(\"periodic task failed. task name is: %s. error is %s\", name, err.Error())\n\t\t}\n\t}\n\n\t_, err = server.scheduler.AddFunc(spec, f)\n\treturn err\n}\n\n// RegisterPeriodicChain register a periodic chain which will be triggered periodically\nfunc (server *Server) RegisterPeriodicChain(spec, name string, signatures ...*tasks.Signature) error {\n\t//check spec\n\tschedule, err := cron.ParseStandard(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf := func() {\n\t\t// new chain\n\t\tchain, _ := tasks.NewChain(tasks.CopySignatures(signatures...)...)\n\n\t\t//get lock\n\t\terr := server.lock.LockWithRetries(utils.GetLockName(name, spec), schedule.Next(time.Now()).UnixNano()-1)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t//send task\n\t\t_, err = server.SendChain(chain)\n\t\tif err != nil {\n\t\t\tlog.ERROR.Printf(\"periodic task failed. task name is: %s. error is %s\", name, err.Error())\n\t\t}\n\t}\n\n\t_, err = server.scheduler.AddFunc(spec, f)\n\treturn err\n}\n\n// RegisterPeriodicGroup register a periodic group which will be triggered periodically\nfunc (server *Server) RegisterPeriodicGroup(spec, name string, sendConcurrency int, signatures ...*tasks.Signature) error {\n\t//check spec\n\tschedule, err := cron.ParseStandard(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf := func() {\n\t\t// new group\n\t\tgroup, _ := tasks.NewGroup(tasks.CopySignatures(signatures...)...)\n\n\t\t//get lock\n\t\terr := server.lock.LockWithRetries(utils.GetLockName(name, spec), schedule.Next(time.Now()).UnixNano()-1)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t//send task\n\t\t_, err = server.SendGroup(group, sendConcurrency)\n\t\tif err != nil {\n\t\t\tlog.ERROR.Printf(\"periodic task failed. task name is: %s. error is %s\", name, err.Error())\n\t\t}\n\t}\n\n\t_, err = server.scheduler.AddFunc(spec, f)\n\treturn err\n}\n\n// RegisterPeriodicChord register a periodic chord which will be triggered periodically\nfunc (server *Server) RegisterPeriodicChord(spec, name string, sendConcurrency int, callback *tasks.Signature, signatures ...*tasks.Signature) error {\n\t//check spec\n\tschedule, err := cron.ParseStandard(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf := func() {\n\t\t// new chord\n\t\tgroup, _ := tasks.NewGroup(tasks.CopySignatures(signatures...)...)\n\t\tchord, _ := tasks.NewChord(group, tasks.CopySignature(callback))\n\n\t\t//get lock\n\t\terr := server.lock.LockWithRetries(utils.GetLockName(name, spec), schedule.Next(time.Now()).UnixNano()-1)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t//send task\n\t\t_, err = server.SendChord(chord, sendConcurrency)\n\t\tif err != nil {\n\t\t\tlog.ERROR.Printf(\"periodic task failed. task name is: %s. error is %s\", name, err.Error())\n\t\t}\n\t}\n\n\t_, err = server.scheduler.AddFunc(spec, f)\n\treturn err\n}\n"
  },
  {
    "path": "v1/server_test.go",
    "content": "package machinery_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\n\t\"github.com/RichardKnop/machinery/v1\"\n\t\"github.com/RichardKnop/machinery/v1/config\"\n)\n\nfunc TestRegisterTasks(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\terr := server.RegisterTasks(map[string]interface{}{\n\t\t\"test_task\": func() error { return nil },\n\t})\n\tassert.NoError(t, err)\n\n\t_, err = server.GetRegisteredTask(\"test_task\")\n\tassert.NoError(t, err, \"test_task is not registered but it should be\")\n}\n\nfunc TestRegisterTask(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\terr := server.RegisterTask(\"test_task\", func() error { return nil })\n\tassert.NoError(t, err)\n\n\t_, err = server.GetRegisteredTask(\"test_task\")\n\tassert.NoError(t, err, \"test_task is not registered but it should be\")\n}\n\nfunc TestRegisterTaskInRaceCondition(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\tfor i:=0; i<10; i++ {\n\t\tgo func() {\n\t\t\terr := server.RegisterTask(\"test_task\", func() error { return nil })\n\t\t\tassert.NoError(t, err)\n\t\t\t_, err = server.GetRegisteredTask(\"test_task\")\n\t\t\tassert.NoError(t, err, \"test_task is not registered but it should be\")\n\t\t}()\n\t}\n}\n\nfunc TestGetRegisteredTask(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\t_, err := server.GetRegisteredTask(\"test_task\")\n\tassert.Error(t, err, \"test_task is registered but it should not be\")\n}\n\nfunc TestGetRegisteredTaskNames(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\n\ttaskName := \"test_task\"\n\terr := server.RegisterTask(taskName, func() error { return nil })\n\tassert.NoError(t, err)\n\n\ttaskNames := server.GetRegisteredTaskNames()\n\tassert.Equal(t, 1, len(taskNames))\n\tassert.Equal(t, taskName, taskNames[0])\n}\n\nfunc TestNewWorker(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\n\tserver.NewWorker(\"test_worker\", 1)\n\tassert.NoError(t, nil)\n}\n\nfunc TestNewCustomQueueWorker(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\n\tserver.NewCustomQueueWorker(\"test_customqueueworker\", 1, \"test_queue\")\n\tassert.NoError(t, nil)\n}\n\nfunc getTestServer(t *testing.T) *machinery.Server {\n\tserver, err := machinery.NewServer(&config.Config{\n\t\tBroker:        \"amqp://guest:guest@localhost:5672/\",\n\t\tDefaultQueue:  \"machinery_tasks\",\n\t\tResultBackend: \"redis://127.0.0.1:6379\",\n\t\tLock:          \"redis://127.0.0.1:6379\",\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"machinery_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"machinery_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\treturn server\n}\n"
  },
  {
    "path": "v1/tasks/errors.go",
    "content": "package tasks\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\n// ErrRetryTaskLater ...\ntype ErrRetryTaskLater struct {\n\tname, msg string\n\tretryIn   time.Duration\n}\n\n// RetryIn returns time.Duration from now when task should be retried\nfunc (e ErrRetryTaskLater) RetryIn() time.Duration {\n\treturn e.retryIn\n}\n\n// Error implements the error interface\nfunc (e ErrRetryTaskLater) Error() string {\n\treturn fmt.Sprintf(\"Task error: %s Will retry in: %s\", e.msg, e.retryIn)\n}\n\n// NewErrRetryTaskLater returns new ErrRetryTaskLater instance\nfunc NewErrRetryTaskLater(msg string, retryIn time.Duration) ErrRetryTaskLater {\n\treturn ErrRetryTaskLater{msg: msg, retryIn: retryIn}\n}\n\n// Retriable is interface that retriable errors should implement\ntype Retriable interface {\n\tRetryIn() time.Duration\n}\n"
  },
  {
    "path": "v1/tasks/reflect.go",
    "content": "package tasks\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar (\n\ttypesMap = map[string]reflect.Type{\n\t\t// base types\n\t\t\"bool\":    reflect.TypeOf(true),\n\t\t\"int\":     reflect.TypeOf(int(1)),\n\t\t\"int8\":    reflect.TypeOf(int8(1)),\n\t\t\"int16\":   reflect.TypeOf(int16(1)),\n\t\t\"int32\":   reflect.TypeOf(int32(1)),\n\t\t\"int64\":   reflect.TypeOf(int64(1)),\n\t\t\"uint\":    reflect.TypeOf(uint(1)),\n\t\t\"uint8\":   reflect.TypeOf(uint8(1)),\n\t\t\"uint16\":  reflect.TypeOf(uint16(1)),\n\t\t\"uint32\":  reflect.TypeOf(uint32(1)),\n\t\t\"uint64\":  reflect.TypeOf(uint64(1)),\n\t\t\"float32\": reflect.TypeOf(float32(0.5)),\n\t\t\"float64\": reflect.TypeOf(float64(0.5)),\n\t\t\"string\":  reflect.TypeOf(string(\"\")),\n\t\t// slices\n\t\t\"[]bool\":    reflect.TypeOf(make([]bool, 0)),\n\t\t\"[]int\":     reflect.TypeOf(make([]int, 0)),\n\t\t\"[]int8\":    reflect.TypeOf(make([]int8, 0)),\n\t\t\"[]int16\":   reflect.TypeOf(make([]int16, 0)),\n\t\t\"[]int32\":   reflect.TypeOf(make([]int32, 0)),\n\t\t\"[]int64\":   reflect.TypeOf(make([]int64, 0)),\n\t\t\"[]uint\":    reflect.TypeOf(make([]uint, 0)),\n\t\t\"[]uint8\":   reflect.TypeOf(make([]uint8, 0)),\n\t\t\"[]uint16\":  reflect.TypeOf(make([]uint16, 0)),\n\t\t\"[]uint32\":  reflect.TypeOf(make([]uint32, 0)),\n\t\t\"[]uint64\":  reflect.TypeOf(make([]uint64, 0)),\n\t\t\"[]float32\": reflect.TypeOf(make([]float32, 0)),\n\t\t\"[]float64\": reflect.TypeOf(make([]float64, 0)),\n\t\t\"[]byte\":    reflect.TypeOf(make([]byte, 0)),\n\t\t\"[]string\":  reflect.TypeOf([]string{\"\"}),\n\t}\n\n\tctxType = reflect.TypeOf((*context.Context)(nil)).Elem()\n\n\ttypeConversionError = func(argValue interface{}, argTypeStr string) error {\n\t\treturn fmt.Errorf(\"%v is not %v\", argValue, argTypeStr)\n\t}\n)\n\n// ErrUnsupportedType ...\ntype ErrUnsupportedType struct {\n\tvalueType string\n}\n\n// NewErrUnsupportedType returns new ErrUnsupportedType\nfunc NewErrUnsupportedType(valueType string) ErrUnsupportedType {\n\treturn ErrUnsupportedType{valueType}\n}\n\n// Error method so we implement the error interface\nfunc (e ErrUnsupportedType) Error() string {\n\treturn fmt.Sprintf(\"%v is not one of supported types\", e.valueType)\n}\n\n// ReflectValue converts interface{} to reflect.Value based on string type\nfunc ReflectValue(valueType string, value interface{}) (reflect.Value, error) {\n\tif strings.HasPrefix(valueType, \"[]\") {\n\t\treturn reflectValues(valueType, value)\n\t}\n\n\treturn reflectValue(valueType, value)\n}\n\n// reflectValue converts interface{} to reflect.Value based on string type\n// representing a base type (not a slice)\nfunc reflectValue(valueType string, value interface{}) (reflect.Value, error) {\n\ttheType, ok := typesMap[valueType]\n\tif !ok {\n\t\treturn reflect.Value{}, NewErrUnsupportedType(valueType)\n\t}\n\ttheValue := reflect.New(theType)\n\n\t// Booleans\n\tif theType.String() == \"bool\" {\n\t\tboolValue, err := getBoolValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetBool(boolValue)\n\t\treturn theValue.Elem(), nil\n\t}\n\n\t// Integers\n\tif strings.HasPrefix(theType.String(), \"int\") {\n\t\tintValue, err := getIntValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetInt(intValue)\n\t\treturn theValue.Elem(), err\n\t}\n\n\t// Unsigned integers\n\tif strings.HasPrefix(theType.String(), \"uint\") {\n\t\tuintValue, err := getUintValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetUint(uintValue)\n\t\treturn theValue.Elem(), err\n\t}\n\n\t// Floating point numbers\n\tif strings.HasPrefix(theType.String(), \"float\") {\n\t\tfloatValue, err := getFloatValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetFloat(floatValue)\n\t\treturn theValue.Elem(), err\n\t}\n\n\t// Strings\n\tif theType.String() == \"string\" {\n\t\tstringValue, err := getStringValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetString(stringValue)\n\t\treturn theValue.Elem(), nil\n\t}\n\n\treturn reflect.Value{}, NewErrUnsupportedType(valueType)\n}\n\n// reflectValues converts interface{} to reflect.Value based on string type\n// representing a slice of values\nfunc reflectValues(valueType string, value interface{}) (reflect.Value, error) {\n\ttheType, ok := typesMap[valueType]\n\tif !ok {\n\t\treturn reflect.Value{}, NewErrUnsupportedType(valueType)\n\t}\n\n\t// For NULL we return an empty slice\n\tif value == nil {\n\t\treturn reflect.MakeSlice(theType, 0, 0), nil\n\t}\n\n\tvar theValue reflect.Value\n\n\t// Booleans\n\tif theType.String() == \"[]bool\" {\n\t\tbools := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, bools.Len(), bools.Len())\n\t\tfor i := 0; i < bools.Len(); i++ {\n\t\t\tboolValue, err := getBoolValue(strings.Split(theType.String(), \"[]\")[1], bools.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetBool(boolValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\t// Integers\n\tif strings.HasPrefix(theType.String(), \"[]int\") {\n\t\tints := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, ints.Len(), ints.Len())\n\t\tfor i := 0; i < ints.Len(); i++ {\n\t\t\tintValue, err := getIntValue(strings.Split(theType.String(), \"[]\")[1], ints.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetInt(intValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\t// Unsigned integers\n\tif strings.HasPrefix(theType.String(), \"[]uint\") || theType.String() == \"[]byte\" {\n\n\t\t// Decode the base64 string if the value type is []uint8 or it's alias []byte\n\t\t// See: https://golang.org/pkg/encoding/json/#Marshal\n\t\t// > Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string\n\t\tif reflect.TypeOf(value).String() == \"string\" {\n\t\t\toutput, err := base64.StdEncoding.DecodeString(value.(string))\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\t\t\tvalue = output\n\t\t}\n\n\t\tuints := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, uints.Len(), uints.Len())\n\t\tfor i := 0; i < uints.Len(); i++ {\n\t\t\tuintValue, err := getUintValue(strings.Split(theType.String(), \"[]\")[1], uints.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetUint(uintValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\t// Floating point numbers\n\tif strings.HasPrefix(theType.String(), \"[]float\") {\n\t\tfloats := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, floats.Len(), floats.Len())\n\t\tfor i := 0; i < floats.Len(); i++ {\n\t\t\tfloatValue, err := getFloatValue(strings.Split(theType.String(), \"[]\")[1], floats.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetFloat(floatValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\t// Strings\n\tif theType.String() == \"[]string\" {\n\t\tstrs := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, strs.Len(), strs.Len())\n\t\tfor i := 0; i < strs.Len(); i++ {\n\t\t\tstrValue, err := getStringValue(strings.Split(theType.String(), \"[]\")[1], strs.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetString(strValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\treturn reflect.Value{}, NewErrUnsupportedType(valueType)\n}\n\nfunc getBoolValue(theType string, value interface{}) (bool, error) {\n\tb, ok := value.(bool)\n\tif !ok {\n\t\treturn false, typeConversionError(value, typesMap[theType].String())\n\t}\n\n\treturn b, nil\n}\n\nfunc getIntValue(theType string, value interface{}) (int64, error) {\n\t// We use https://golang.org/pkg/encoding/json/#Decoder.UseNumber when unmarshaling signatures.\n\t// This is because JSON only supports 64-bit floating point numbers and we could lose precision\n\t// when converting from float64 to signed integer\n\tif strings.HasPrefix(fmt.Sprintf(\"%T\", value), \"json.Number\") {\n\t\tn, ok := value.(json.Number)\n\t\tif !ok {\n\t\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t\t}\n\n\t\treturn n.Int64()\n\t}\n\n\tn, ok := value.(int64)\n\tif !ok {\n\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t}\n\n\treturn n, nil\n}\n\nfunc getUintValue(theType string, value interface{}) (uint64, error) {\n\t// Losing precision only happens in receiving a JSON number from a language like js,\n\t// and receiving a large uint number from golang or python could cause json.Number.Int64 be turned into a panic.\n\t// So we use strconv.ParseUint to correctly parse a uint value.\n\tif strings.HasPrefix(fmt.Sprintf(\"%T\", value), \"json.Number\") {\n\t\tn, ok := value.(json.Number)\n\t\tif !ok {\n\t\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t\t}\n\n\t\tuintVal, err := strconv.ParseUint(string(n), 10, 64)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\treturn uintVal, nil\n\t}\n\n\tvar n uint64\n\tswitch value := value.(type) {\n\tcase uint64:\n\t\tn = value\n\tcase uint8:\n\t\tn = uint64(value)\n\tdefault:\n\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t}\n\treturn n, nil\n}\n\nfunc getFloatValue(theType string, value interface{}) (float64, error) {\n\t// We use https://golang.org/pkg/encoding/json/#Decoder.UseNumber when unmarshaling signatures.\n\t// This is because JSON only supports 64-bit floating point numbers and we could lose precision\n\tif strings.HasPrefix(fmt.Sprintf(\"%T\", value), \"json.Number\") {\n\t\tn, ok := value.(json.Number)\n\t\tif !ok {\n\t\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t\t}\n\n\t\treturn n.Float64()\n\t}\n\n\tf, ok := value.(float64)\n\tif !ok {\n\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t}\n\n\treturn f, nil\n}\n\nfunc getStringValue(theType string, value interface{}) (string, error) {\n\ts, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", typeConversionError(value, typesMap[theType].String())\n\t}\n\n\treturn s, nil\n}\n\n// IsContextType checks to see if the type is a context.Context\nfunc IsContextType(t reflect.Type) bool {\n\treturn t == ctxType\n}\n"
  },
  {
    "path": "v1/tasks/reflect_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n)\n\nvar (\n\treflectValuesTestCases = []struct {\n\t\tname          string\n\t\tvalue         interface{}\n\t\texpectedType  string\n\t\texpectedValue interface{}\n\t}{\n\t\t// basic types\n\t\t{\n\t\t\tname:         \"bool\",\n\t\t\tvalue:        false,\n\t\t\texpectedType: \"bool\",\n\t\t},\n\t\t{\n\t\t\tname:          \"int\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"int\",\n\t\t\texpectedValue: int(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"int8\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"int8\",\n\t\t\texpectedValue: int8(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"int16\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"int16\",\n\t\t\texpectedValue: int16(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"int32\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"int32\",\n\t\t\texpectedValue: int32(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"int64\",\n\t\t\tvalue:         json.Number(\"185135722552891243\"),\n\t\t\texpectedType:  \"int64\",\n\t\t\texpectedValue: int64(185135722552891243),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"uint\",\n\t\t\texpectedValue: uint(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint8\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"uint8\",\n\t\t\texpectedValue: uint8(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint16\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"uint16\",\n\t\t\texpectedValue: uint16(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint32\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"uint32\",\n\t\t\texpectedValue: uint32(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint64\",\n\t\t\tvalue:         json.Number(\"185135722552891243\"),\n\t\t\texpectedType:  \"uint64\",\n\t\t\texpectedValue: uint64(185135722552891243),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint64\",\n\t\t\tvalue:         json.Number(\"9223372036854775808\"), // math.MaxInt64 + 1\n\t\t\texpectedType:  \"uint64\",\n\t\t\texpectedValue: uint64(9223372036854775808),\n\t\t},\n\t\t{\n\t\t\tname:          \"float32\",\n\t\t\tvalue:         json.Number(\"0.5\"),\n\t\t\texpectedType:  \"float32\",\n\t\t\texpectedValue: float32(0.5),\n\t\t},\n\t\t{\n\t\t\tname:          \"float64\",\n\t\t\tvalue:         json.Number(\"0.5\"),\n\t\t\texpectedType:  \"float64\",\n\t\t\texpectedValue: float64(0.5),\n\t\t},\n\t\t{\n\t\t\tname:          \"string\",\n\t\t\tvalue:         \"123\",\n\t\t\texpectedType:  \"string\",\n\t\t\texpectedValue: \"123\",\n\t\t},\n\t\t// slices\n\t\t{\n\t\t\tname:          \"[]bool\",\n\t\t\tvalue:         []interface{}{false, true},\n\t\t\texpectedType:  \"[]bool\",\n\t\t\texpectedValue: []bool{false, true},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int\",\n\t\t\texpectedValue: []int{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int8\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int8\",\n\t\t\texpectedValue: []int8{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int16\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int16\",\n\t\t\texpectedValue: []int16{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int32\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int32\",\n\t\t\texpectedValue: []int32{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int64\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int64\",\n\t\t\texpectedValue: []int64{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint\",\n\t\t\texpectedValue: []uint{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint8\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint8\",\n\t\t\texpectedValue: []uint8{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint16\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint16\",\n\t\t\texpectedValue: []uint16{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint32\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint32\",\n\t\t\texpectedValue: []uint32{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint64\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint64\",\n\t\t\texpectedValue: []uint64{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]float32\",\n\t\t\tvalue:         []interface{}{json.Number(\"0.5\"), json.Number(\"1.28\")},\n\t\t\texpectedType:  \"[]float32\",\n\t\t\texpectedValue: []float32{0.5, 1.28},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]float64\",\n\t\t\tvalue:         []interface{}{json.Number(\"0.5\"), json.Number(\"1.28\")},\n\t\t\texpectedType:  \"[]float64\",\n\t\t\texpectedValue: []float64{0.5, 1.28},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]string\",\n\t\t\tvalue:         []interface{}{\"foo\", \"bar\"},\n\t\t\texpectedType:  \"[]string\",\n\t\t\texpectedValue: []string{\"foo\", \"bar\"},\n\t\t},\n\t\t// empty slices from NULL\n\t\t{\n\t\t\tname:          \"[]bool\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]bool\",\n\t\t\texpectedValue: []bool{},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int64\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]int64\",\n\t\t\texpectedValue: []int64{},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint64\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]uint64\",\n\t\t\texpectedValue: []uint64{},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]float64\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]float64\",\n\t\t\texpectedValue: []float64{},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]string\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]string\",\n\t\t\texpectedValue: []string{},\n\t\t},\n\t}\n)\n\nfunc TestReflectValue(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, tc := range reflectValuesTestCases {\n\t\ttc := tc // capture range variable\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tvalue, err := tasks.ReflectValue(tc.name, tc.value)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif value.Type().String() != tc.expectedType {\n\t\t\t\tt.Errorf(\"type is %v, want %s\", value.Type().String(), tc.expectedType)\n\t\t\t}\n\t\t\tif tc.expectedValue != nil {\n\t\t\t\tif !reflect.DeepEqual(value.Interface(), tc.expectedValue) {\n\t\t\t\t\tt.Errorf(\"value is %v, want %v\", value.Interface(), tc.expectedValue)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "v1/tasks/result.go",
    "content": "package tasks\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n)\n\n// TaskResult represents an actual return value of a processed task\ntype TaskResult struct {\n\tType  string      `bson:\"type\"`\n\tValue interface{} `bson:\"value\"`\n}\n\n// ReflectTaskResults ...\nfunc ReflectTaskResults(taskResults []*TaskResult) ([]reflect.Value, error) {\n\tresultValues := make([]reflect.Value, len(taskResults))\n\tfor i, taskResult := range taskResults {\n\t\tresultValue, err := ReflectValue(taskResult.Type, taskResult.Value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresultValues[i] = resultValue\n\t}\n\treturn resultValues, nil\n}\n\n// HumanReadableResults ...\nfunc HumanReadableResults(results []reflect.Value) string {\n\tif len(results) == 1 {\n\t\treturn fmt.Sprintf(\"%v\", results[0].Interface())\n\t}\n\n\treadableResults := make([]string, len(results))\n\tfor i := 0; i < len(results); i++ {\n\t\treadableResults[i] = fmt.Sprintf(\"%v\", results[i].Interface())\n\t}\n\n\treturn fmt.Sprintf(\"[%s]\", strings.Join(readableResults, \", \"))\n}\n"
  },
  {
    "path": "v1/tasks/result_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestReflectTaskResults(t *testing.T) {\n\tt.Parallel()\n\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  \"[]string\",\n\t\t\tValue: []string{\"f\", \"o\", \"o\"},\n\t\t},\n\t}\n\tresults, err := tasks.ReflectTaskResults(taskResults)\n\tif assert.NoError(t, err) {\n\t\tassert.Equal(t, 1, len(results))\n\t\tassert.Equal(t, 3, results[0].Len())\n\t\tassert.Equal(t, \"f\", results[0].Index(0).String())\n\t\tassert.Equal(t, \"o\", results[0].Index(1).String())\n\t\tassert.Equal(t, \"o\", results[0].Index(2).String())\n\t}\n}\n"
  },
  {
    "path": "v1/tasks/signature.go",
    "content": "package tasks\n\nimport (\n\t\"fmt\"\n\t\"github.com/RichardKnop/machinery/v1/utils\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Arg represents a single argument passed to invocation fo a task\ntype Arg struct {\n\tName  string      `bson:\"name\"`\n\tType  string      `bson:\"type\"`\n\tValue interface{} `bson:\"value\"`\n}\n\n// Headers represents the headers which should be used to direct the task\ntype Headers map[string]interface{}\n\n// Set on Headers implements opentracing.TextMapWriter for trace propagation\nfunc (h Headers) Set(key, val string) {\n\th[key] = val\n}\n\n// ForeachKey on Headers implements opentracing.TextMapReader for trace propagation.\n// It is essentially the same as the opentracing.TextMapReader implementation except\n// for the added casting from interface{} to string.\nfunc (h Headers) ForeachKey(handler func(key, val string) error) error {\n\tfor k, v := range h {\n\t\t// Skip any non string values\n\t\tstringValue, ok := v.(string)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := handler(k, stringValue); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Signature represents a single task invocation\ntype Signature struct {\n\tUUID           string\n\tName           string\n\tRoutingKey     string\n\tETA            *time.Time\n\tGroupUUID      string\n\tGroupTaskCount int\n\tArgs           []Arg\n\tHeaders        Headers\n\tPriority       uint8\n\tImmutable      bool\n\tRetryCount     int\n\tRetryTimeout   int\n\tOnSuccess      []*Signature\n\tOnError        []*Signature\n\tChordCallback  *Signature\n\t//MessageGroupId for Broker, e.g. SQS\n\tBrokerMessageGroupId string\n\t//ReceiptHandle of SQS Message\n\tSQSReceiptHandle string\n\t// StopTaskDeletionOnError used with sqs when we want to send failed messages to dlq,\n\t// and don't want machinery to delete from source queue\n\tStopTaskDeletionOnError bool\n\t// IgnoreWhenTaskNotRegistered auto removes the request when there is no handeler available\n\t// When this is true a task with no handler will be ignored and not placed back in the queue\n\tIgnoreWhenTaskNotRegistered bool\n}\n\n// NewSignature creates a new task signature\nfunc NewSignature(name string, args []Arg) (*Signature, error) {\n\tsignatureID := uuid.New().String()\n\treturn &Signature{\n\t\tUUID: fmt.Sprintf(\"task_%v\", signatureID),\n\t\tName: name,\n\t\tArgs: args,\n\t}, nil\n}\n\nfunc CopySignatures(signatures ...*Signature) []*Signature {\n\tvar sigs = make([]*Signature, len(signatures))\n\tfor index, signature := range signatures {\n\t\tsigs[index] = CopySignature(signature)\n\t}\n\treturn sigs\n}\n\nfunc CopySignature(signature *Signature) *Signature {\n\tvar sig = new(Signature)\n\t_ = utils.DeepCopy(sig, signature)\n\treturn sig\n}\n"
  },
  {
    "path": "v1/tasks/state.go",
    "content": "package tasks\n\nimport \"time\"\n\nconst (\n\t// StatePending - initial state of a task\n\tStatePending = \"PENDING\"\n\t// StateReceived - when task is received by a worker\n\tStateReceived = \"RECEIVED\"\n\t// StateStarted - when the worker starts processing the task\n\tStateStarted = \"STARTED\"\n\t// StateRetry - when failed task has been scheduled for retry\n\tStateRetry = \"RETRY\"\n\t// StateSuccess - when the task is processed successfully\n\tStateSuccess = \"SUCCESS\"\n\t// StateFailure - when processing of the task fails\n\tStateFailure = \"FAILURE\"\n)\n\n// TaskState represents a state of a task\ntype TaskState struct {\n\tTaskUUID  string        `bson:\"_id\"`\n\tTaskName  string        `bson:\"task_name\"`\n\tState     string        `bson:\"state\"`\n\tResults   []*TaskResult `bson:\"results\"`\n\tError     string        `bson:\"error\"`\n\tCreatedAt time.Time     `bson:\"created_at\"`\n\tTTL       int64         `bson:\"ttl,omitempty\"`\n}\n\n// GroupMeta stores useful metadata about tasks within the same group\n// E.g. UUIDs of all tasks which are used in order to check if all tasks\n// completed successfully or not and thus whether to trigger chord callback\ntype GroupMeta struct {\n\tGroupUUID      string    `bson:\"_id\"`\n\tTaskUUIDs      []string  `bson:\"task_uuids\"`\n\tChordTriggered bool      `bson:\"chord_triggered\"`\n\tLock           bool      `bson:\"lock\"`\n\tCreatedAt      time.Time `bson:\"created_at\"`\n\tTTL            int64     `bson:\"ttl,omitempty\"`\n}\n\n// NewPendingTaskState ...\nfunc NewPendingTaskState(signature *Signature) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID:  signature.UUID,\n\t\tTaskName:  signature.Name,\n\t\tState:     StatePending,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n}\n\n// NewReceivedTaskState ...\nfunc NewReceivedTaskState(signature *Signature) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateReceived,\n\t}\n}\n\n// NewStartedTaskState ...\nfunc NewStartedTaskState(signature *Signature) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateStarted,\n\t}\n}\n\n// NewSuccessTaskState ...\nfunc NewSuccessTaskState(signature *Signature, results []*TaskResult) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateSuccess,\n\t\tResults:  results,\n\t}\n}\n\n// NewFailureTaskState ...\nfunc NewFailureTaskState(signature *Signature, err string) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateFailure,\n\t\tError:    err,\n\t}\n}\n\n// NewRetryTaskState ...\nfunc NewRetryTaskState(signature *Signature) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateRetry,\n\t}\n}\n\n// IsCompleted returns true if state is SUCCESS or FAILURE,\n// i.e. the task has finished processing and either succeeded or failed.\nfunc (taskState *TaskState) IsCompleted() bool {\n\treturn taskState.IsSuccess() || taskState.IsFailure()\n}\n\n// IsSuccess returns true if state is SUCCESS\nfunc (taskState *TaskState) IsSuccess() bool {\n\treturn taskState.State == StateSuccess\n}\n\n// IsFailure returns true if state is FAILURE\nfunc (taskState *TaskState) IsFailure() bool {\n\treturn taskState.State == StateFailure\n}\n"
  },
  {
    "path": "v1/tasks/state_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTaskStateIsCompleted(t *testing.T) {\n\tt.Parallel()\n\n\ttaskState := &tasks.TaskState{\n\t\tTaskUUID: \"taskUUID\",\n\t\tState:    tasks.StatePending,\n\t}\n\n\tassert.False(t, taskState.IsCompleted())\n\n\ttaskState.State = tasks.StateReceived\n\tassert.False(t, taskState.IsCompleted())\n\n\ttaskState.State = tasks.StateStarted\n\tassert.False(t, taskState.IsCompleted())\n\n\ttaskState.State = tasks.StateSuccess\n\tassert.True(t, taskState.IsCompleted())\n\n\ttaskState.State = tasks.StateFailure\n\tassert.True(t, taskState.IsCompleted())\n}\n"
  },
  {
    "path": "v1/tasks/task.go",
    "content": "package tasks\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"runtime/debug\"\n\n\topentracing \"github.com/opentracing/opentracing-go\"\n\topentracing_ext \"github.com/opentracing/opentracing-go/ext\"\n\topentracing_log \"github.com/opentracing/opentracing-go/log\"\n\n\t\"github.com/RichardKnop/machinery/v1/log\"\n)\n\n// ErrTaskPanicked ...\nvar ErrTaskPanicked = errors.New(\"Invoking task caused a panic\")\n\n// Task wraps a signature and methods used to reflect task arguments and\n// return values after invoking the task\ntype Task struct {\n\tTaskFunc   reflect.Value\n\tUseContext bool\n\tContext    context.Context\n\tArgs       []reflect.Value\n}\n\ntype signatureCtxType struct{}\n\nvar signatureCtx signatureCtxType\n\n// SignatureFromContext gets the signature from the context\nfunc SignatureFromContext(ctx context.Context) *Signature {\n\tif ctx == nil {\n\t\treturn nil\n\t}\n\n\tv := ctx.Value(signatureCtx)\n\tif v == nil {\n\t\treturn nil\n\t}\n\n\tsignature, _ := v.(*Signature)\n\treturn signature\n}\n\n// NewWithSignature is the same as New but injects the signature\nfunc NewWithSignature(taskFunc interface{}, signature *Signature) (*Task, error) {\n\targs := signature.Args\n\tctx := context.Background()\n\tctx = context.WithValue(ctx, signatureCtx, signature)\n\ttask := &Task{\n\t\tTaskFunc: reflect.ValueOf(taskFunc),\n\t\tContext:  ctx,\n\t}\n\n\ttaskFuncType := reflect.TypeOf(taskFunc)\n\tif taskFuncType.NumIn() > 0 {\n\t\targ0Type := taskFuncType.In(0)\n\t\tif IsContextType(arg0Type) {\n\t\t\ttask.UseContext = true\n\t\t}\n\t}\n\n\tif err := task.ReflectArgs(args); err != nil {\n\t\treturn nil, fmt.Errorf(\"Reflect task args error: %s\", err)\n\t}\n\n\treturn task, nil\n}\n\n// New tries to use reflection to convert the function and arguments\n// into a reflect.Value and prepare it for invocation\nfunc New(taskFunc interface{}, args []Arg) (*Task, error) {\n\ttask := &Task{\n\t\tTaskFunc: reflect.ValueOf(taskFunc),\n\t\tContext:  context.Background(),\n\t}\n\n\ttaskFuncType := reflect.TypeOf(taskFunc)\n\tif taskFuncType.NumIn() > 0 {\n\t\targ0Type := taskFuncType.In(0)\n\t\tif IsContextType(arg0Type) {\n\t\t\ttask.UseContext = true\n\t\t}\n\t}\n\n\tif err := task.ReflectArgs(args); err != nil {\n\t\treturn nil, fmt.Errorf(\"Reflect task args error: %s\", err)\n\t}\n\n\treturn task, nil\n}\n\n// Call attempts to call the task with the supplied arguments.\n//\n// `err` is set in the return value in two cases:\n// 1. The reflected function invocation panics (e.g. due to a mismatched\n//    argument list).\n// 2. The task func itself returns a non-nil error.\nfunc (t *Task) Call() (taskResults []*TaskResult, err error) {\n\t// retrieve the span from the task's context and finish it as soon as this function returns\n\tif span := opentracing.SpanFromContext(t.Context); span != nil {\n\t\tdefer span.Finish()\n\t}\n\n\tdefer func() {\n\t\t// Recover from panic and set err.\n\t\tif e := recover(); e != nil {\n\t\t\tswitch e := e.(type) {\n\t\t\tdefault:\n\t\t\t\terr = ErrTaskPanicked\n\t\t\tcase error:\n\t\t\t\terr = e\n\t\t\tcase string:\n\t\t\t\terr = errors.New(e)\n\t\t\t}\n\n\t\t\t// mark the span as failed and dump the error and stack trace to the span\n\t\t\tif span := opentracing.SpanFromContext(t.Context); span != nil {\n\t\t\t\topentracing_ext.Error.Set(span, true)\n\t\t\t\tspan.LogFields(\n\t\t\t\t\topentracing_log.Error(err),\n\t\t\t\t\topentracing_log.Object(\"stack\", string(debug.Stack())),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Print stack trace\n\t\t\tlog.ERROR.Printf(\"%v stack: %s\", err, debug.Stack())\n\t\t}\n\t}()\n\n\targs := t.Args\n\n\tif t.UseContext {\n\t\tctxValue := reflect.ValueOf(t.Context)\n\t\targs = append([]reflect.Value{ctxValue}, args...)\n\t}\n\n\t// Invoke the task\n\tresults := t.TaskFunc.Call(args)\n\n\t// Task must return at least a value\n\tif len(results) == 0 {\n\t\treturn nil, ErrTaskReturnsNoValue\n\t}\n\n\t// Last returned value\n\tlastResult := results[len(results)-1]\n\n\t// If the last returned value is not nil, it has to be of error type, if that\n\t// is not the case, return error message, otherwise propagate the task error\n\t// to the caller\n\tif !lastResult.IsNil() {\n\t\t// If the result implements Retriable interface, return instance of Retriable\n\t\tretriableErrorInterface := reflect.TypeOf((*Retriable)(nil)).Elem()\n\t\tif lastResult.Type().Implements(retriableErrorInterface) {\n\t\t\treturn nil, lastResult.Interface().(ErrRetryTaskLater)\n\t\t}\n\n\t\t// Otherwise, check that the result implements the standard error interface,\n\t\t// if not, return ErrLastReturnValueMustBeError error\n\t\terrorInterface := reflect.TypeOf((*error)(nil)).Elem()\n\t\tif !lastResult.Type().Implements(errorInterface) {\n\t\t\treturn nil, ErrLastReturnValueMustBeError\n\t\t}\n\n\t\t// Return the standard error\n\t\treturn nil, lastResult.Interface().(error)\n\t}\n\n\t// Convert reflect values to task results\n\ttaskResults = make([]*TaskResult, len(results)-1)\n\tfor i := 0; i < len(results)-1; i++ {\n\t\tval := results[i].Interface()\n\t\ttypeStr := reflect.TypeOf(val).String()\n\t\ttaskResults[i] = &TaskResult{\n\t\t\tType:  typeStr,\n\t\t\tValue: val,\n\t\t}\n\t}\n\n\treturn taskResults, err\n}\n\n// ReflectArgs converts []TaskArg to []reflect.Value\nfunc (t *Task) ReflectArgs(args []Arg) error {\n\targValues := make([]reflect.Value, len(args))\n\n\tfor i, arg := range args {\n\t\targValue, err := ReflectValue(arg.Type, arg.Value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\targValues[i] = argValue\n\t}\n\n\tt.Args = argValues\n\treturn nil\n}\n"
  },
  {
    "path": "v1/tasks/task_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTaskCallErrorTest(t *testing.T) {\n\tt.Parallel()\n\n\t// Create test task that returns tasks.ErrRetryTaskLater error\n\tretriable := func() error { return tasks.NewErrRetryTaskLater(\"some error\", 4*time.Hour) }\n\n\ttask, err := tasks.New(retriable, []tasks.Arg{})\n\tassert.NoError(t, err)\n\n\t// Invoke TryCall and validate that returned error can be cast to tasks.ErrRetryTaskLater\n\tresults, err := task.Call()\n\tassert.Nil(t, results)\n\tassert.NotNil(t, err)\n\t_, ok := interface{}(err).(tasks.ErrRetryTaskLater)\n\tassert.True(t, ok, \"Error should be castable to tasks.ErrRetryTaskLater\")\n\n\t// Create test task that returns a standard error\n\tstandard := func() error { return errors.New(\"some error\") }\n\n\ttask, err = tasks.New(standard, []tasks.Arg{})\n\tassert.NoError(t, err)\n\n\t// Invoke TryCall and validate that returned error is standard\n\tresults, err = task.Call()\n\tassert.Nil(t, results)\n\tassert.NotNil(t, err)\n\tassert.Equal(t, \"some error\", err.Error())\n}\n\nfunc TestTaskReflectArgs(t *testing.T) {\n\tt.Parallel()\n\n\ttask := new(tasks.Task)\n\targs := []tasks.Arg{\n\t\t{\n\t\t\tType:  \"[]int64\",\n\t\t\tValue: []int64{1, 2},\n\t\t},\n\t}\n\n\terr := task.ReflectArgs(args)\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(task.Args))\n\tassert.Equal(t, \"[]int64\", task.Args[0].Type().String())\n}\n\nfunc TestTaskCallInvalidArgRobustnessError(t *testing.T) {\n\tt.Parallel()\n\n\t// Create a test task function\n\tf := func(x int) error { return nil }\n\n\t// Construct an invalid argument list and reflect it\n\targs := []tasks.Arg{\n\t\t{Type: \"bool\", Value: true},\n\t}\n\n\ttask, err := tasks.New(f, args)\n\tassert.NoError(t, err)\n\n\t// Invoke TryCall and validate error handling\n\tresults, err := task.Call()\n\tassert.Equal(t, \"reflect: Call using bool as type int\", err.Error())\n\tassert.Nil(t, results)\n}\n\nfunc TestTaskCallInterfaceValuedResult(t *testing.T) {\n\tt.Parallel()\n\n\t// Create a test task function\n\tf := func() (interface{}, error) { return math.Pi, nil }\n\n\ttask, err := tasks.New(f, []tasks.Arg{})\n\tassert.NoError(t, err)\n\n\ttaskResults, err := task.Call()\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"float64\", taskResults[0].Type)\n\tassert.Equal(t, math.Pi, taskResults[0].Value)\n}\n\nfunc TestTaskCallWithContext(t *testing.T) {\n\tt.Parallel()\n\n\tf := func(c context.Context) (interface{}, error) {\n\t\tassert.NotNil(t, c)\n\t\tassert.Nil(t, tasks.SignatureFromContext(c))\n\t\treturn math.Pi, nil\n\t}\n\ttask, err := tasks.New(f, []tasks.Arg{})\n\tassert.NoError(t, err)\n\ttaskResults, err := task.Call()\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"float64\", taskResults[0].Type)\n\tassert.Equal(t, math.Pi, taskResults[0].Value)\n}\n\nfunc TestTaskCallWithSignatureInContext(t *testing.T) {\n\tt.Parallel()\n\n\tf := func(c context.Context) (interface{}, error) {\n\t\tassert.NotNil(t, c)\n\t\tsignature := tasks.SignatureFromContext(c)\n\t\tassert.NotNil(t, signature)\n\t\tassert.Equal(t, \"foo\", signature.Name)\n\t\treturn math.Pi, nil\n\t}\n\tsignature, err := tasks.NewSignature(\"foo\", []tasks.Arg{})\n\tassert.NoError(t, err)\n\ttask, err := tasks.NewWithSignature(f, signature)\n\tassert.NoError(t, err)\n\ttaskResults, err := task.Call()\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"float64\", taskResults[0].Type)\n\tassert.Equal(t, math.Pi, taskResults[0].Value)\n}\n"
  },
  {
    "path": "v1/tasks/validate.go",
    "content": "package tasks\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n)\n\nvar (\n\t// ErrTaskMustBeFunc ...\n\tErrTaskMustBeFunc = errors.New(\"Task must be a func type\")\n\t// ErrTaskReturnsNoValue ...\n\tErrTaskReturnsNoValue = errors.New(\"Task must return at least a single value\")\n\t// ErrLastReturnValueMustBeError ..\n\tErrLastReturnValueMustBeError = errors.New(\"Last return value of a task must be error\")\n)\n\n// ValidateTask validates task function using reflection and makes sure\n// it has a proper signature. Functions used as tasks must return at least a\n// single value and the last return type must be error\nfunc ValidateTask(task interface{}) error {\n\tv := reflect.ValueOf(task)\n\tt := v.Type()\n\n\t// Task must be a function\n\tif t.Kind() != reflect.Func {\n\t\treturn ErrTaskMustBeFunc\n\t}\n\n\t// Task must return at least a single value\n\tif t.NumOut() < 1 {\n\t\treturn ErrTaskReturnsNoValue\n\t}\n\n\t// Last return value must be error\n\tlastReturnType := t.Out(t.NumOut() - 1)\n\terrorInterface := reflect.TypeOf((*error)(nil)).Elem()\n\tif !lastReturnType.Implements(errorInterface) {\n\t\treturn ErrLastReturnValueMustBeError\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "v1/tasks/validate_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestValidateTask(t *testing.T) {\n\tt.Parallel()\n\n\ttype someStruct struct{}\n\tvar (\n\t\ttaskOfWrongType                   = new(someStruct)\n\t\ttaskWithoutReturnValue            = func() {}\n\t\ttaskWithoutErrorAsLastReturnValue = func() int { return 0 }\n\t\tvalidTask                         = func(arg string) error { return nil }\n\t)\n\n\terr := tasks.ValidateTask(taskOfWrongType)\n\tassert.Equal(t, tasks.ErrTaskMustBeFunc, err)\n\n\terr = tasks.ValidateTask(taskWithoutReturnValue)\n\tassert.Equal(t, tasks.ErrTaskReturnsNoValue, err)\n\n\terr = tasks.ValidateTask(taskWithoutErrorAsLastReturnValue)\n\tassert.Equal(t, tasks.ErrLastReturnValueMustBeError, err)\n\n\terr = tasks.ValidateTask(validTask)\n\tassert.NoError(t, err)\n}\n"
  },
  {
    "path": "v1/tasks/workflow.go",
    "content": "package tasks\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Chain creates a chain of tasks to be executed one after another\ntype Chain struct {\n\tTasks []*Signature\n}\n\n// Group creates a set of tasks to be executed in parallel\ntype Group struct {\n\tGroupUUID string\n\tTasks     []*Signature\n}\n\n// Chord adds an optional callback to the group to be executed\n// after all tasks in the group finished\ntype Chord struct {\n\tGroup    *Group\n\tCallback *Signature\n}\n\n// GetUUIDs returns slice of task UUIDS\nfunc (group *Group) GetUUIDs() []string {\n\ttaskUUIDs := make([]string, len(group.Tasks))\n\tfor i, signature := range group.Tasks {\n\t\ttaskUUIDs[i] = signature.UUID\n\t}\n\treturn taskUUIDs\n}\n\n// NewChain creates a new chain of tasks to be processed one by one, passing\n// results unless task signatures are set to be immutable\nfunc NewChain(signatures ...*Signature) (*Chain, error) {\n\t// Auto generate task UUIDs if needed\n\tfor _, signature := range signatures {\n\t\tif signature.UUID == \"\" {\n\t\t\tsignatureID := uuid.New().String()\n\t\t\tsignature.UUID = fmt.Sprintf(\"task_%v\", signatureID)\n\t\t}\n\t}\n\n\tfor i := len(signatures) - 1; i > 0; i-- {\n\t\tif i > 0 {\n\t\t\tsignatures[i-1].OnSuccess = []*Signature{signatures[i]}\n\t\t}\n\t}\n\n\tchain := &Chain{Tasks: signatures}\n\n\treturn chain, nil\n}\n\n// NewGroup creates a new group of tasks to be processed in parallel\nfunc NewGroup(signatures ...*Signature) (*Group, error) {\n\t// Generate a group UUID\n\tgroupUUID := uuid.New().String()\n\tgroupID := fmt.Sprintf(\"group_%v\", groupUUID)\n\n\t// Auto generate task UUIDs if needed, group tasks by common group UUID\n\tfor _, signature := range signatures {\n\t\tif signature.UUID == \"\" {\n\t\t\tsignatureID := uuid.New().String()\n\t\t\tsignature.UUID = fmt.Sprintf(\"task_%v\", signatureID)\n\t\t}\n\t\tsignature.GroupUUID = groupID\n\t\tsignature.GroupTaskCount = len(signatures)\n\t}\n\n\treturn &Group{\n\t\tGroupUUID: groupID,\n\t\tTasks:     signatures,\n\t}, nil\n}\n\n// NewChord creates a new chord (a group of tasks with a single callback\n// to be executed after all tasks in the group has completed)\nfunc NewChord(group *Group, callback *Signature) (*Chord, error) {\n\tif callback.UUID == \"\" {\n\t\t// Generate a UUID for the chord callback\n\t\tcallbackUUID := uuid.New().String()\n\t\tcallback.UUID = fmt.Sprintf(\"chord_%v\", callbackUUID)\n\t}\n\n\t// Add a chord callback to all tasks\n\tfor _, signature := range group.Tasks {\n\t\tsignature.ChordCallback = callback\n\t}\n\n\treturn &Chord{Group: group, Callback: callback}, nil\n}\n"
  },
  {
    "path": "v1/tasks/workflow_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewChain(t *testing.T) {\n\tt.Parallel()\n\n\ttask1 := tasks.Signature{\n\t\tName: \"foo\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(1),\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(1),\n\t\t\t},\n\t\t},\n\t}\n\n\ttask2 := tasks.Signature{\n\t\tName: \"bar\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(5),\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(6),\n\t\t\t},\n\t\t},\n\t}\n\n\ttask3 := tasks.Signature{\n\t\tName: \"qux\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(4),\n\t\t\t},\n\t\t},\n\t}\n\n\tchain, err := tasks.NewChain(&task1, &task2, &task3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfirstTask := chain.Tasks[0]\n\n\tassert.Equal(t, \"foo\", firstTask.Name)\n\tassert.Equal(t, \"bar\", firstTask.OnSuccess[0].Name)\n\tassert.Equal(t, \"qux\", firstTask.OnSuccess[0].OnSuccess[0].Name)\n}\n"
  },
  {
    "path": "v1/tracing/tracing.go",
    "content": "package tracing\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\n\topentracing \"github.com/opentracing/opentracing-go\"\n\topentracing_ext \"github.com/opentracing/opentracing-go/ext\"\n\topentracing_log \"github.com/opentracing/opentracing-go/log\"\n)\n\n// opentracing tags\nvar (\n\tMachineryTag     = opentracing.Tag{Key: string(opentracing_ext.Component), Value: \"machinery\"}\n\tWorkflowGroupTag = opentracing.Tag{Key: \"machinery.workflow\", Value: \"group\"}\n\tWorkflowChordTag = opentracing.Tag{Key: \"machinery.workflow\", Value: \"chord\"}\n\tWorkflowChainTag = opentracing.Tag{Key: \"machinery.workflow\", Value: \"chain\"}\n)\n\n// StartSpanFromHeaders will extract a span from the signature headers\n// and start a new span with the given operation name.\nfunc StartSpanFromHeaders(headers tasks.Headers, operationName string) opentracing.Span {\n\t// Try to extract the span context from the carrier.\n\tspanContext, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, headers)\n\n\t// Create a new span from the span context if found or start a new trace with the function name.\n\t// For clarity add the machinery component tag.\n\tspan := opentracing.StartSpan(\n\t\toperationName,\n\t\tConsumerOption(spanContext),\n\t\tMachineryTag,\n\t)\n\n\t// Log any error but don't fail\n\tif err != nil {\n\t\tspan.LogFields(opentracing_log.Error(err))\n\t}\n\n\treturn span\n}\n\n// HeadersWithSpan will inject a span into the signature headers\nfunc HeadersWithSpan(headers tasks.Headers, span opentracing.Span) tasks.Headers {\n\t// check if the headers aren't nil\n\tif headers == nil {\n\t\theaders = make(tasks.Headers)\n\t}\n\n\tif err := opentracing.GlobalTracer().Inject(span.Context(), opentracing.TextMap, headers); err != nil {\n\t\tspan.LogFields(opentracing_log.Error(err))\n\t}\n\n\treturn headers\n}\n\ntype consumerOption struct {\n\tproducerContext opentracing.SpanContext\n}\n\nfunc (c consumerOption) Apply(o *opentracing.StartSpanOptions) {\n\tif c.producerContext != nil {\n\t\topentracing.FollowsFrom(c.producerContext).Apply(o)\n\t}\n\topentracing_ext.SpanKindConsumer.Apply(o)\n}\n\n// ConsumerOption ...\nfunc ConsumerOption(producer opentracing.SpanContext) opentracing.StartSpanOption {\n\treturn consumerOption{producer}\n}\n\ntype producerOption struct{}\n\nfunc (p producerOption) Apply(o *opentracing.StartSpanOptions) {\n\topentracing_ext.SpanKindProducer.Apply(o)\n}\n\n// ProducerOption ...\nfunc ProducerOption() opentracing.StartSpanOption {\n\treturn producerOption{}\n}\n\n// AnnotateSpanWithSignatureInfo ...\nfunc AnnotateSpanWithSignatureInfo(span opentracing.Span, signature *tasks.Signature) {\n\t// tag the span with some info about the signature\n\tspan.SetTag(\"signature.name\", signature.Name)\n\tspan.SetTag(\"signature.uuid\", signature.UUID)\n\n\tif signature.GroupUUID != \"\" {\n\t\tspan.SetTag(\"signature.group.uuid\", signature.GroupUUID)\n\t}\n\n\tif signature.ChordCallback != nil {\n\t\tspan.SetTag(\"signature.chord.callback.uuid\", signature.ChordCallback.UUID)\n\t\tspan.SetTag(\"signature.chord.callback.name\", signature.ChordCallback.Name)\n\t}\n}\n\n// AnnotateSpanWithChainInfo ...\nfunc AnnotateSpanWithChainInfo(span opentracing.Span, chain *tasks.Chain) {\n\t// tag the span with some info about the chain\n\tspan.SetTag(\"chain.tasks.length\", len(chain.Tasks))\n\n\t// inject the tracing span into the tasks signature headers\n\tfor _, signature := range chain.Tasks {\n\t\tsignature.Headers = HeadersWithSpan(signature.Headers, span)\n\t}\n}\n\n// AnnotateSpanWithGroupInfo ...\nfunc AnnotateSpanWithGroupInfo(span opentracing.Span, group *tasks.Group, sendConcurrency int) {\n\t// tag the span with some info about the group\n\tspan.SetTag(\"group.uuid\", group.GroupUUID)\n\tspan.SetTag(\"group.tasks.length\", len(group.Tasks))\n\tspan.SetTag(\"group.concurrency\", sendConcurrency)\n\n\t// encode the task uuids to json, if that fails just dump it in\n\tif taskUUIDs, err := json.Marshal(group.GetUUIDs()); err == nil {\n\t\tspan.SetTag(\"group.tasks\", string(taskUUIDs))\n\t} else {\n\t\tspan.SetTag(\"group.tasks\", group.GetUUIDs())\n\t}\n\n\t// inject the tracing span into the tasks signature headers\n\tfor _, signature := range group.Tasks {\n\t\tsignature.Headers = HeadersWithSpan(signature.Headers, span)\n\t}\n}\n\n// AnnotateSpanWithChordInfo ...\nfunc AnnotateSpanWithChordInfo(span opentracing.Span, chord *tasks.Chord, sendConcurrency int) {\n\t// tag the span with chord specific info\n\tspan.SetTag(\"chord.callback.uuid\", chord.Callback.UUID)\n\n\t// inject the tracing span into the callback signature\n\tchord.Callback.Headers = HeadersWithSpan(chord.Callback.Headers, span)\n\n\t// tag the span for the group part of the chord\n\tAnnotateSpanWithGroupInfo(span, chord.Group, sendConcurrency)\n}\n"
  },
  {
    "path": "v1/utils/deepcopy.go",
    "content": "package utils\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n)\n\nvar (\n\tErrNoMatchType     = errors.New(\"no match type\")\n\tErrNoPointer       = errors.New(\"must be interface\")\n\tErrInvalidArgument = errors.New(\"invalid arguments\")\n)\n\nfunc deepCopy(dst, src reflect.Value) {\n\tswitch src.Kind() {\n\tcase reflect.Interface:\n\t\tvalue := src.Elem()\n\t\tif !value.IsValid() {\n\t\t\treturn\n\t\t}\n\t\tnewValue := reflect.New(value.Type()).Elem()\n\t\tdeepCopy(newValue, value)\n\t\tdst.Set(newValue)\n\tcase reflect.Ptr:\n\t\tvalue := src.Elem()\n\t\tif !value.IsValid() {\n\t\t\treturn\n\t\t}\n\t\tdst.Set(reflect.New(value.Type()))\n\t\tdeepCopy(dst.Elem(), value)\n\tcase reflect.Map:\n\t\tdst.Set(reflect.MakeMap(src.Type()))\n\t\tkeys := src.MapKeys()\n\t\tfor _, key := range keys {\n\t\t\tvalue := src.MapIndex(key)\n\t\t\tnewValue := reflect.New(value.Type()).Elem()\n\t\t\tdeepCopy(newValue, value)\n\t\t\tdst.SetMapIndex(key, newValue)\n\t\t}\n\tcase reflect.Slice:\n\t\tdst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))\n\t\tfor i := 0; i < src.Len(); i++ {\n\t\t\tdeepCopy(dst.Index(i), src.Index(i))\n\t\t}\n\tcase reflect.Struct:\n\t\ttypeSrc := src.Type()\n\t\tfor i := 0; i < src.NumField(); i++ {\n\t\t\tvalue := src.Field(i)\n\t\t\ttag := typeSrc.Field(i).Tag\n\t\t\tif value.CanSet() && tag.Get(\"deepcopy\") != \"-\" {\n\t\t\t\tdeepCopy(dst.Field(i), value)\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tdst.Set(src)\n\t}\n}\n\nfunc DeepCopy(dst, src interface{}) error {\n\ttypeDst := reflect.TypeOf(dst)\n\ttypeSrc := reflect.TypeOf(src)\n\tif typeDst != typeSrc {\n\t\treturn ErrNoMatchType\n\t}\n\tif typeSrc.Kind() != reflect.Ptr {\n\t\treturn ErrNoPointer\n\t}\n\n\tvalueDst := reflect.ValueOf(dst).Elem()\n\tvalueSrc := reflect.ValueOf(src).Elem()\n\tif !valueDst.IsValid() || !valueSrc.IsValid() {\n\t\treturn ErrInvalidArgument\n\t}\n\n\tdeepCopy(valueDst, valueSrc)\n\treturn nil\n}\n\nfunc DeepClone(v interface{}) interface{} {\n\tdst := reflect.New(reflect.TypeOf(v)).Elem()\n\tdeepCopy(dst, reflect.ValueOf(v))\n\treturn dst.Interface()\n}\n"
  },
  {
    "path": "v1/utils/deepcopy_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDeepCopy(t *testing.T) {\n\tt.Parallel()\n\n\ttype s struct {\n\t\tA float64\n\t\tB int\n\t\tC []int\n\t\tD *int\n\t\tE map[string]int\n\t}\n\tvar d = 3\n\tvar dst = new(s)\n\tvar src = s{1.0, 1, []int{1, 2, 3}, &d, map[string]int{\"a\": 1}}\n\n\terr := DeepCopy(dst, &src)\n\tsrc.A = 2\n\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1.0, dst.A)\n\tassert.Equal(t, 1, dst.B)\n\tassert.Equal(t, []int{1, 2, 3}, dst.C)\n\tassert.Equal(t, &d, dst.D)\n\tassert.Equal(t, map[string]int{\"a\": 1}, dst.E)\n}\n"
  },
  {
    "path": "v1/utils/utils.go",
    "content": "package utils\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\tLockKeyPrefix = \"machinery_lock_\"\n)\n\nfunc GetLockName(name, spec string) string {\n\treturn LockKeyPrefix + filepath.Base(os.Args[0]) + name + spec\n}\n"
  },
  {
    "path": "v1/utils/utils_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetLockName(t *testing.T) {\n\tt.Parallel()\n\n\tlockName := GetLockName(\"test\", \"*/3 * * *\")\n\tassert.Equal(t, \"machinery_lock_utils.testtest*/3 * * *\", lockName)\n}\n"
  },
  {
    "path": "v1/utils/uuid.go",
    "content": "package utils\n\nimport (\n\t\"github.com/google/uuid\"\n\t\"strings\"\n)\n\nfunc GetPureUUID() string {\n\tuid, _ := uuid.NewUUID()\n\treturn strings.Replace(uid.String(), \"-\", \"\", -1)\n}\n"
  },
  {
    "path": "v1/utils/uuid_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetPureUUID(t *testing.T) {\n\tt.Parallel()\n\n\tassert.Len(t, GetPureUUID(), 32)\n}\n"
  },
  {
    "path": "v1/worker.go",
    "content": "package machinery\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/opentracing/opentracing-go\"\n\n\t\"github.com/RichardKnop/machinery/v1/backends/amqp\"\n\t\"github.com/RichardKnop/machinery/v1/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v1/log\"\n\t\"github.com/RichardKnop/machinery/v1/retry\"\n\t\"github.com/RichardKnop/machinery/v1/tasks\"\n\t\"github.com/RichardKnop/machinery/v1/tracing\"\n)\n\n// Worker represents a single worker process\ntype Worker struct {\n\tserver            *Server\n\tConsumerTag       string\n\tConcurrency       int\n\tQueue             string\n\terrorHandler      func(err error)\n\tpreTaskHandler    func(*tasks.Signature)\n\tpostTaskHandler   func(*tasks.Signature)\n\tpreConsumeHandler func(*Worker) bool\n}\n\nvar (\n\t// ErrWorkerQuitGracefully is returned when worker quit gracefully\n\tErrWorkerQuitGracefully = errors.New(\"Worker quit gracefully\")\n\t// ErrWorkerQuitAbruptly is returned when worker quit abruptly\n\tErrWorkerQuitAbruptly = errors.New(\"Worker quit abruptly\")\n)\n\n// Launch starts a new worker process. The worker subscribes\n// to the default queue and processes incoming registered tasks\nfunc (worker *Worker) Launch() error {\n\terrorsChan := make(chan error)\n\n\tworker.LaunchAsync(errorsChan)\n\n\treturn <-errorsChan\n}\n\n// LaunchAsync is a non blocking version of Launch\nfunc (worker *Worker) LaunchAsync(errorsChan chan<- error) {\n\tcnf := worker.server.GetConfig()\n\tbroker := worker.server.GetBroker()\n\n\t// Log some useful information about worker configuration\n\tlog.INFO.Printf(\"Launching a worker with the following settings:\")\n\tlog.INFO.Printf(\"- Broker: %s\", RedactURL(cnf.Broker))\n\tif worker.Queue == \"\" {\n\t\tlog.INFO.Printf(\"- DefaultQueue: %s\", cnf.DefaultQueue)\n\t} else {\n\t\tlog.INFO.Printf(\"- CustomQueue: %s\", worker.Queue)\n\t}\n\tlog.INFO.Printf(\"- ResultBackend: %s\", RedactURL(cnf.ResultBackend))\n\tif cnf.AMQP != nil {\n\t\tlog.INFO.Printf(\"- AMQP: %s\", cnf.AMQP.Exchange)\n\t\tlog.INFO.Printf(\"  - Exchange: %s\", cnf.AMQP.Exchange)\n\t\tlog.INFO.Printf(\"  - ExchangeType: %s\", cnf.AMQP.ExchangeType)\n\t\tlog.INFO.Printf(\"  - BindingKey: %s\", cnf.AMQP.BindingKey)\n\t\tlog.INFO.Printf(\"  - PrefetchCount: %d\", cnf.AMQP.PrefetchCount)\n\t}\n\n\tvar signalWG sync.WaitGroup\n\t// Goroutine to start broker consumption and handle retries when broker connection dies\n\tgo func() {\n\t\tfor {\n\t\t\tretry, err := broker.StartConsuming(worker.ConsumerTag, worker.Concurrency, worker)\n\n\t\t\tif retry {\n\t\t\t\tif worker.errorHandler != nil {\n\t\t\t\t\tworker.errorHandler(err)\n\t\t\t\t} else {\n\t\t\t\t\tlog.WARNING.Printf(\"Broker failed with error: %s\", err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsignalWG.Wait()\n\t\t\t\terrorsChan <- err // stop the goroutine\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tif !cnf.NoUnixSignals {\n\t\tsig := make(chan os.Signal, 1)\n\t\tsignal.Notify(sig, os.Interrupt, syscall.SIGTERM)\n\t\tvar signalsReceived uint\n\n\t\t// Goroutine Handle SIGINT and SIGTERM signals\n\t\tgo func() {\n\t\t\tfor s := range sig {\n\t\t\t\tlog.WARNING.Printf(\"Signal received: %v\", s)\n\t\t\t\tsignalsReceived++\n\n\t\t\t\tif signalsReceived < 2 {\n\t\t\t\t\t// After first Ctrl+C start quitting the worker gracefully\n\t\t\t\t\tlog.WARNING.Print(\"Waiting for running tasks to finish before shutting down\")\n\t\t\t\t\tsignalWG.Add(1)\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tworker.Quit()\n\t\t\t\t\t\terrorsChan <- ErrWorkerQuitGracefully\n\t\t\t\t\t\tsignalWG.Done()\n\t\t\t\t\t}()\n\t\t\t\t} else {\n\t\t\t\t\t// Abort the program when user hits Ctrl+C second time in a row\n\t\t\t\t\terrorsChan <- ErrWorkerQuitAbruptly\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n}\n\n// CustomQueue returns Custom Queue of the running worker process\nfunc (worker *Worker) CustomQueue() string {\n\treturn worker.Queue\n}\n\n// Quit tears down the running worker process\nfunc (worker *Worker) Quit() {\n\tworker.server.GetBroker().StopConsuming()\n}\n\n// Process handles received tasks and triggers success/error callbacks\nfunc (worker *Worker) Process(signature *tasks.Signature) error {\n\t// If the task is not registered with this worker, do not continue\n\t// but only return nil as we do not want to restart the worker process\n\tif !worker.server.IsTaskRegistered(signature.Name) {\n\t\treturn nil\n\t}\n\n\ttaskFunc, err := worker.server.GetRegisteredTask(signature.Name)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\t// Update task state to RECEIVED\n\tif err = worker.server.GetBackend().SetStateReceived(signature); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'received' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t// Prepare task for processing\n\ttask, err := tasks.NewWithSignature(taskFunc, signature)\n\t// if this failed, it means the task is malformed, probably has invalid\n\t// signature, go directly to task failed without checking whether to retry\n\tif err != nil {\n\t\tworker.taskFailed(signature, err)\n\t\treturn err\n\t}\n\n\t// try to extract trace span from headers and add it to the function context\n\t// so it can be used inside the function if it has context.Context as the first\n\t// argument. Start a new span if it isn't found.\n\ttaskSpan := tracing.StartSpanFromHeaders(signature.Headers, signature.Name)\n\ttracing.AnnotateSpanWithSignatureInfo(taskSpan, signature)\n\ttask.Context = opentracing.ContextWithSpan(task.Context, taskSpan)\n\n\t// Update task state to STARTED\n\tif err = worker.server.GetBackend().SetStateStarted(signature); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'started' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t//Run handler before the task is called\n\tif worker.preTaskHandler != nil {\n\t\tworker.preTaskHandler(signature)\n\t}\n\n\t//Defer run handler for the end of the task\n\tif worker.postTaskHandler != nil {\n\t\tdefer worker.postTaskHandler(signature)\n\t}\n\n\t// Call the task\n\tresults, err := task.Call()\n\tif err != nil {\n\t\t// If a tasks.ErrRetryTaskLater was returned from the task,\n\t\t// retry the task after specified duration\n\t\tretriableErr, ok := interface{}(err).(tasks.ErrRetryTaskLater)\n\t\tif ok {\n\t\t\treturn worker.retryTaskIn(signature, retriableErr.RetryIn())\n\t\t}\n\n\t\t// Otherwise, execute default retry logic based on signature.RetryCount\n\t\t// and signature.RetryTimeout values\n\t\tif signature.RetryCount > 0 {\n\t\t\treturn worker.taskRetry(signature)\n\t\t}\n\n\t\treturn worker.taskFailed(signature, err)\n\t}\n\n\treturn worker.taskSucceeded(signature, results)\n}\n\n// retryTask decrements RetryCount counter and republishes the task to the queue\nfunc (worker *Worker) taskRetry(signature *tasks.Signature) error {\n\t// Update task state to RETRY\n\tif err := worker.server.GetBackend().SetStateRetry(signature); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'retry' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t// Decrement the retry counter, when it reaches 0, we won't retry again\n\tsignature.RetryCount--\n\n\t// Increase retry timeout\n\tsignature.RetryTimeout = retry.FibonacciNext(signature.RetryTimeout)\n\n\t// Delay task by signature.RetryTimeout seconds\n\teta := time.Now().UTC().Add(time.Second * time.Duration(signature.RetryTimeout))\n\tsignature.ETA = &eta\n\n\tlog.WARNING.Printf(\"Task %s failed. Going to retry in %d seconds.\", signature.UUID, signature.RetryTimeout)\n\n\t// Send the task back to the queue\n\t_, err := worker.server.SendTask(signature)\n\treturn err\n}\n\n// taskRetryIn republishes the task to the queue with ETA of now + retryIn.Seconds()\nfunc (worker *Worker) retryTaskIn(signature *tasks.Signature, retryIn time.Duration) error {\n\t// Update task state to RETRY\n\tif err := worker.server.GetBackend().SetStateRetry(signature); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'retry' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t// Delay task by retryIn duration\n\teta := time.Now().UTC().Add(retryIn)\n\tsignature.ETA = &eta\n\n\tlog.WARNING.Printf(\"Task %s failed. Going to retry in %.0f seconds.\", signature.UUID, retryIn.Seconds())\n\n\t// Send the task back to the queue\n\t_, err := worker.server.SendTask(signature)\n\treturn err\n}\n\n// taskSucceeded updates the task state and triggers success callbacks or a\n// chord callback if this was the last task of a group with a chord callback\nfunc (worker *Worker) taskSucceeded(signature *tasks.Signature, taskResults []*tasks.TaskResult) error {\n\t// Update task state to SUCCESS\n\tif err := worker.server.GetBackend().SetStateSuccess(signature, taskResults); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'success' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t// Log human readable results of the processed task\n\tvar debugResults = \"[]\"\n\tresults, err := tasks.ReflectTaskResults(taskResults)\n\tif err != nil {\n\t\tlog.WARNING.Print(err)\n\t} else {\n\t\tdebugResults = tasks.HumanReadableResults(results)\n\t}\n\tlog.DEBUG.Printf(\"Processed task %s. Results = %s\", signature.UUID, debugResults)\n\n\t// Trigger success callbacks\n\n\tfor _, successTask := range signature.OnSuccess {\n\t\tif signature.Immutable == false {\n\t\t\t// Pass results of the task to success callbacks\n\t\t\tfor _, taskResult := range taskResults {\n\t\t\t\tsuccessTask.Args = append(successTask.Args, tasks.Arg{\n\t\t\t\t\tType:  taskResult.Type,\n\t\t\t\t\tValue: taskResult.Value,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tworker.server.SendTask(successTask)\n\t}\n\n\t// If the task was not part of a group, just return\n\tif signature.GroupUUID == \"\" {\n\t\treturn nil\n\t}\n\n\t// There is no chord callback, just return\n\tif signature.ChordCallback == nil {\n\t\treturn nil\n\t}\n\n\t// Check if all task in the group has completed\n\tgroupCompleted, err := worker.server.GetBackend().GroupCompleted(\n\t\tsignature.GroupUUID,\n\t\tsignature.GroupTaskCount,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Completed check for group %s returned error: %s\", signature.GroupUUID, err)\n\t}\n\n\t// If the group has not yet completed, just return\n\tif !groupCompleted {\n\t\treturn nil\n\t}\n\n\t// Defer purging of group meta queue if we are using AMQP backend\n\tif worker.hasAMQPBackend() {\n\t\tdefer worker.server.GetBackend().PurgeGroupMeta(signature.GroupUUID)\n\t}\n\n\t// Trigger chord callback\n\tshouldTrigger, err := worker.server.GetBackend().TriggerChord(signature.GroupUUID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Triggering chord for group %s returned error: %s\", signature.GroupUUID, err)\n\t}\n\n\t// Chord has already been triggered\n\tif !shouldTrigger {\n\t\treturn nil\n\t}\n\n\t// Get task states\n\ttaskStates, err := worker.server.GetBackend().GroupTaskStates(\n\t\tsignature.GroupUUID,\n\t\tsignature.GroupTaskCount,\n\t)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\n\t\t\t\"Failed to get tasks states for group:[%s]. Task count:[%d]. The chord may not be triggered. Error:[%s]\",\n\t\t\tsignature.GroupUUID,\n\t\t\tsignature.GroupTaskCount,\n\t\t\terr,\n\t\t)\n\t\treturn nil\n\t}\n\n\t// Append group tasks' return values to chord task if it's not immutable\n\tfor _, taskState := range taskStates {\n\t\tif !taskState.IsSuccess() {\n\t\t\treturn nil\n\t\t}\n\n\t\tif signature.ChordCallback.Immutable == false {\n\t\t\t// Pass results of the task to the chord callback\n\t\t\tfor _, taskResult := range taskState.Results {\n\t\t\t\tsignature.ChordCallback.Args = append(signature.ChordCallback.Args, tasks.Arg{\n\t\t\t\t\tType:  taskResult.Type,\n\t\t\t\t\tValue: taskResult.Value,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// Send the chord task\n\t_, err = worker.server.SendTask(signature.ChordCallback)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// taskFailed updates the task state and triggers error callbacks\nfunc (worker *Worker) taskFailed(signature *tasks.Signature, taskErr error) error {\n\t// Update task state to FAILURE\n\tif err := worker.server.GetBackend().SetStateFailure(signature, taskErr.Error()); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'failure' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\tif worker.errorHandler != nil {\n\t\tworker.errorHandler(taskErr)\n\t} else {\n\t\tlog.ERROR.Printf(\"Failed processing task %s. Error = %v\", signature.UUID, taskErr)\n\t}\n\n\t// Trigger error callbacks\n\tfor _, errorTask := range signature.OnError {\n\t\t// Pass error as a first argument to error callbacks\n\t\targs := append([]tasks.Arg{{\n\t\t\tType:  \"string\",\n\t\t\tValue: taskErr.Error(),\n\t\t}}, errorTask.Args...)\n\t\terrorTask.Args = args\n\t\tworker.server.SendTask(errorTask)\n\t}\n\n\tif signature.StopTaskDeletionOnError {\n\t\treturn errs.ErrStopTaskDeletion\n\t}\n\n\treturn nil\n}\n\n// Returns true if the worker uses AMQP backend\nfunc (worker *Worker) hasAMQPBackend() bool {\n\t_, ok := worker.server.GetBackend().(*amqp.Backend)\n\treturn ok\n}\n\n// SetErrorHandler sets a custom error handler for task errors\n// A default behavior is just to log the error after all the retry attempts fail\nfunc (worker *Worker) SetErrorHandler(handler func(err error)) {\n\tworker.errorHandler = handler\n}\n\n//SetPreTaskHandler sets a custom handler func before a job is started\nfunc (worker *Worker) SetPreTaskHandler(handler func(*tasks.Signature)) {\n\tworker.preTaskHandler = handler\n}\n\n//SetPostTaskHandler sets a custom handler for the end of a job\nfunc (worker *Worker) SetPostTaskHandler(handler func(*tasks.Signature)) {\n\tworker.postTaskHandler = handler\n}\n\n//SetPreConsumeHandler sets a custom handler for the end of a job\nfunc (worker *Worker) SetPreConsumeHandler(handler func(*Worker) bool) {\n\tworker.preConsumeHandler = handler\n}\n\n//GetServer returns server\nfunc (worker *Worker) GetServer() *Server {\n\treturn worker.server\n}\n\n//\nfunc (worker *Worker) PreConsumeHandler() bool {\n\tif worker.preConsumeHandler == nil {\n\t\treturn true\n\t}\n\n\treturn worker.preConsumeHandler(worker)\n}\n\nfunc RedactURL(urlString string) string {\n\tu, err := url.Parse(urlString)\n\tif err != nil {\n\t\treturn urlString\n\t}\n\treturn fmt.Sprintf(\"%s://%s\", u.Scheme, u.Host)\n}\n"
  },
  {
    "path": "v1/worker_test.go",
    "content": "package machinery_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/RichardKnop/machinery/v1\"\n)\n\nfunc TestRedactURL(t *testing.T) {\n\tt.Parallel()\n\n\tbroker := \"amqp://guest:guest@localhost:5672\"\n\tredactedURL := machinery.RedactURL(broker)\n\tassert.Equal(t, \"amqp://localhost:5672\", redactedURL)\n}\n\nfunc TestPreConsumeHandler(t *testing.T) {\n\tt.Parallel()\n\t\n\tworker := &machinery.Worker{}\n\n\tworker.SetPreConsumeHandler(SamplePreConsumeHandler)\n\tassert.True(t, worker.PreConsumeHandler())\n}\n\nfunc SamplePreConsumeHandler(w *machinery.Worker) bool {\n\treturn true\n}\n"
  },
  {
    "path": "v2/Dockerfile.test",
    "content": "# Start from a Debian image with the latest version of Go installed\n# and a workspace (GOPATH) configured at /go.\nFROM golang\n\n# Set environment variables\nENV PATH /go/bin:$PATH\n\n# Cd into the source code directory\nWORKDIR /go/src/github.com/RichardKnop/machinery/v2\n\n# Copy the local package files to the container's workspace.\nADD . /go/src/github.com/RichardKnop/machinery/v2\n\n# Run integration tests as default command\nCMD /go/src/github.com/RichardKnop/machinery/v2/wait-for-it.sh rabbitmq:5672 -- make test-with-coverage\n"
  },
  {
    "path": "v2/Makefile",
    "content": ".PHONY: fmt lint golint test test-with-coverage ci\n# TODO: When Go 1.9 is released vendor folder should be ignored automatically\nPACKAGES=`go list ./... | grep -v vendor | grep -v mocks`\n\nfmt:\n\tfor pkg in ${PACKAGES}; do \\\n\t\tgo fmt $$pkg; \\\n\tdone;\n\nlint:\n\tgometalinter --tests --disable-all --deadline=120s -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode ./...\n\ngolint:\n\tfor pkg in ${PACKAGES}; do \\\n\t\tgolint -set_exit_status $$pkg || GOLINT_FAILED=1; \\\n\tdone; \\\n\t[ -z \"$$GOLINT_FAILED\" ]\n\ntest:\n\tTEST_FAILED= ; \\\n\tfor pkg in ${PACKAGES}; do \\\n\t\tgo test $$pkg || TEST_FAILED=1; \\\n\tdone; \\\n\t[ -z \"$$TEST_FAILED\" ]\n\ntest-with-coverage:\n\techo \"\" > coverage.out\n\techo \"mode: set\" > coverage-all.out\n\tTEST_FAILED= ; \\\n\tfor pkg in ${PACKAGES}; do \\\n\t\tgo test -coverprofile=coverage.out -covermode=set $$pkg || TEST_FAILED=1; \\\n\t\ttail -n +2 coverage.out >> coverage-all.out; \\\n\tdone; \\\n\t[ -z \"$$TEST_FAILED\" ]\n\t#go tool cover -html=coverage-all.out\n\nci:\n\tbash -c 'docker-compose -f docker-compose.test.yml -p machinery_ci up --build --abort-on-container-exit --exit-code-from sut'\n"
  },
  {
    "path": "v2/backends/amqp/amqp.go",
    "content": "package amqp\n\n// NOTE: Using AMQP as a result backend is quite tricky since every time we\n// read a message from the queue keeping task states, the message is removed\n// from the queue. This leads to problems with keeping a reliable state of a\n// group of tasks since concurrent processes updating the group state cause\n// race conditions and inconsistent state.\n//\n// This is avoided by a \"clever\" hack. A special queue identified by a group\n// UUID is created and we store serialised TaskState objects of successfully\n// completed tasks. By inspecting the queue we can then say:\n// 1) If all group tasks finished (number of unacked messages = group task count)\n// 2) If all group tasks finished AND succeeded (by consuming the queue)\n//\n// It is important to consume the queue exclusively to avoid race conditions.\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\n// Backend represents an AMQP result backend\ntype Backend struct {\n\tcommon.Backend\n\tcommon.AMQPConnector\n}\n\n// New creates Backend instance\nfunc New(cnf *config.Config) iface.Backend {\n\treturn &Backend{Backend: common.NewBackend(cnf), AMQPConnector: common.AMQPConnector{}}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\treturn nil\n}\n\n// GroupCompleted returns true if all tasks in a group finished\n// NOTE: Given AMQP limitation this will only return true if all finished\n// tasks were successful as we do not keep track of completed failed tasks\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tqueueState, err := b.InspectQueue(channel, groupUUID)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\treturn queueState.Messages == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tqueueState, err := b.InspectQueue(channel, groupUUID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif queueState.Messages != groupTaskCount {\n\t\treturn nil, fmt.Errorf(\"Already consumed: %v\", err)\n\t}\n\n\tdeliveries, err := channel.Consume(\n\t\tgroupUUID, // queue name\n\t\t\"\",        // consumer tag\n\t\tfalse,     // auto-ack\n\t\ttrue,      // exclusive\n\t\tfalse,     // no-local\n\t\tfalse,     // no-wait\n\t\tnil,       // arguments\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Queue consume error: %s\", err)\n\t}\n\n\tstates := make([]*tasks.TaskState, groupTaskCount)\n\tfor i := 0; i < groupTaskCount; i++ {\n\t\td := <-deliveries\n\n\t\tstate := new(tasks.TaskState)\n\t\tdecoder := json.NewDecoder(bytes.NewReader([]byte(d.Body)))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(state); err != nil {\n\t\t\td.Nack(false, false) // multiple, requeue\n\t\t\treturn nil, err\n\t\t}\n\n\t\td.Ack(false) // multiple\n\n\t\tstates[i] = state\n\t}\n\n\treturn states, nil\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer b.Close(channel, conn)\n\n\t_, err = b.InspectQueue(channel, amqmChordTriggeredQueue(groupUUID))\n\tif err != nil {\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\ttaskState := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\ttaskState := tasks.NewStartedTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tstate := tasks.NewRetryTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\n\tif err := b.updateState(taskState); err != nil {\n\t\treturn err\n\t}\n\n\tif signature.GroupUUID == \"\" {\n\t\treturn nil\n\t}\n\n\treturn b.markTaskCompleted(signature, taskState)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\n\tif err := b.updateState(taskState); err != nil {\n\t\treturn err\n\t}\n\n\tif signature.GroupUUID == \"\" {\n\t\treturn nil\n\t}\n\n\treturn b.markTaskCompleted(signature, taskState)\n}\n\n// GetState returns the latest task state. It will only return the status once\n// as the message will get consumed and removed from the queue.\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\tdeclareQueueArgs := amqp.Table{\n\t\t// Time in milliseconds\n\t\t// after that message will expire\n\t\t\"x-message-ttl\": int32(b.getExpiresIn()),\n\t\t// Time after that the queue will be deleted.\n\t\t\"x-expires\": int32(b.getExpiresIn()),\n\t}\n\tconn, channel, _, _, _, err := b.Connect(\n\t\tb.GetConfig().ResultBackend,\n\t\t\"\",\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\ttaskUUID,                        // queue name\n\t\tfalse,                           // queue durable\n\t\ttrue,                            // queue delete when unused\n\t\ttaskUUID,                        // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tdeclareQueueArgs,                // queue declare args\n\t\tnil,                             // queue binding args\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer b.Close(channel, conn)\n\n\td, ok, err := channel.Get(\n\t\ttaskUUID, // queue name\n\t\tfalse,    // multiple\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !ok {\n\t\treturn nil, errors.New(\"No state ready\")\n\t}\n\n\td.Ack(false)\n\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader([]byte(d.Body)))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\tlog.ERROR.Printf(\"Failed to unmarshal task state: %s\", string(d.Body))\n\t\tlog.ERROR.Print(err)\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer b.Close(channel, conn)\n\n\treturn b.DeleteQueue(channel, taskUUID)\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\tconn, channel, err := b.Open(b.GetConfig().ResultBackend, b.GetConfig().TLSConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tb.DeleteQueue(channel, amqmChordTriggeredQueue(groupUUID))\n\n\treturn b.DeleteQueue(channel, groupUUID)\n}\n\n// updateState saves current task state\nfunc (b *Backend) updateState(taskState *tasks.TaskState) error {\n\tmessage, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tdeclareQueueArgs := amqp.Table{\n\t\t// Time in milliseconds\n\t\t// after that message will expire\n\t\t\"x-message-ttl\": int32(b.getExpiresIn()),\n\t\t// Time after that the queue will be deleted.\n\t\t\"x-expires\": int32(b.getExpiresIn()),\n\t}\n\tconn, channel, queue, confirmsChan, _, err := b.Connect(\n\t\tb.GetConfig().ResultBackend,\n\t\t\"\",\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\ttaskState.TaskUUID,              // queue name\n\t\tfalse,                           // queue durable\n\t\ttrue,                            // queue delete when unused\n\t\ttaskState.TaskUUID,              // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tdeclareQueueArgs,                // queue declare args\n\t\tnil,                             // queue binding args\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tif err := channel.Publish(\n\t\tb.GetConfig().AMQP.Exchange, // exchange\n\t\tqueue.Name,                  // routing key\n\t\tfalse,                       // mandatory\n\t\tfalse,                       // immediate\n\t\tamqp.Publishing{\n\t\t\tContentType:  \"application/json\",\n\t\t\tBody:         message,\n\t\t\tDeliveryMode: amqp.Persistent, // Persistent // Transient\n\t\t},\n\t); err != nil {\n\t\treturn err\n\t}\n\n\tconfirmed := <-confirmsChan\n\n\tif confirmed.Ack {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"Failed delivery of delivery tag: %d\", confirmed.DeliveryTag)\n}\n\n// getExpiresIn returns expiration time\nfunc (b *Backend) getExpiresIn() int {\n\tresultsExpireIn := b.GetConfig().ResultsExpireIn * 1000\n\tif resultsExpireIn == 0 {\n\t\t// // expire results after 1 hour by default\n\t\tresultsExpireIn = config.DefaultResultsExpireIn * 1000\n\t}\n\treturn resultsExpireIn\n}\n\n// markTaskCompleted marks task as completed in either groupdUUID_success\n// or groupUUID_failure queue. This is important for GroupCompleted and\n// GroupSuccessful methods\nfunc (b *Backend) markTaskCompleted(signature *tasks.Signature, taskState *tasks.TaskState) error {\n\tif signature.GroupUUID == \"\" || signature.GroupTaskCount == 0 {\n\t\treturn nil\n\t}\n\n\tmessage, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tdeclareQueueArgs := amqp.Table{\n\t\t// Time in milliseconds\n\t\t// after that message will expire\n\t\t\"x-message-ttl\": int32(b.getExpiresIn()),\n\t\t// Time after that the queue will be deleted.\n\t\t\"x-expires\": int32(b.getExpiresIn()),\n\t}\n\tconn, channel, queue, confirmsChan, _, err := b.Connect(\n\t\tb.GetConfig().ResultBackend,\n\t\t\"\",\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\tsignature.GroupUUID,             // queue name\n\t\tfalse,                           // queue durable\n\t\ttrue,                            // queue delete when unused\n\t\tsignature.GroupUUID,             // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tdeclareQueueArgs,                // queue declare args\n\t\tnil,                             // queue binding args\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tif err := channel.Publish(\n\t\tb.GetConfig().AMQP.Exchange, // exchange\n\t\tqueue.Name,                  // routing key\n\t\tfalse,                       // mandatory\n\t\tfalse,                       // immediate\n\t\tamqp.Publishing{\n\t\t\tContentType:  \"application/json\",\n\t\t\tBody:         message,\n\t\t\tDeliveryMode: amqp.Persistent, // Persistent // Transient\n\t\t},\n\t); err != nil {\n\t\treturn err\n\t}\n\n\tconfirmed := <-confirmsChan\n\n\tif !confirmed.Ack {\n\t\treturn fmt.Errorf(\"Failed delivery of delivery tag: %v\", confirmed.DeliveryTag)\n\t}\n\n\treturn nil\n}\n\nfunc amqmChordTriggeredQueue(groupUUID string) string {\n\treturn fmt.Sprintf(\"%s_chord_triggered\", groupUUID)\n}\n"
  },
  {
    "path": "v2/backends/amqp/amqp_test.go",
    "content": "package amqp_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/amqp\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar (\n\tamqpConfig *config.Config\n)\n\nfunc init() {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tif amqpURL == \"\" {\n\t\treturn\n\t}\n\n\tfinalAmqpURL := amqpURL\n\tvar finalSeparator string\n\n\tamqpURLs := os.Getenv(\"AMQP_URLS\")\n\tif amqpURLs != \"\" {\n\t\tseparator := os.Getenv(\"AMQP_URLS_SEPARATOR\")\n\t\tif separator == \"\" {\n\t\t\treturn\n\t\t}\n\t\tfinalSeparator = separator\n\t\tfinalAmqpURL = amqpURLs\n\t}\n\n\tamqp2URL := os.Getenv(\"AMQP2_URL\")\n\tif amqp2URL == \"\" {\n\t\tamqp2URL = amqpURL\n\t}\n\n\tamqpConfig = &config.Config{\n\t\tBroker:                  finalAmqpURL,\n\t\tMultipleBrokerSeparator: finalSeparator,\n\t\tDefaultQueue:            \"test_queue\",\n\t\tResultBackend:           amqp2URL,\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t}\n}\n\nfunc TestGroupCompleted(t *testing.T) {\n\tif os.Getenv(\"AMQP_URL\") == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tgroupUUID := \"testGroupUUID\"\n\tgroupTaskCount := 2\n\ttask1 := &tasks.Signature{\n\t\tUUID:           \"testTaskUUID1\",\n\t\tGroupUUID:      groupUUID,\n\t\tGroupTaskCount: groupTaskCount,\n\t}\n\ttask2 := &tasks.Signature{\n\t\tUUID:           \"testTaskUUID2\",\n\t\tGroupUUID:      groupUUID,\n\t\tGroupTaskCount: groupTaskCount,\n\t}\n\n\tbackend := amqp.New(amqpConfig)\n\n\t// Cleanup before the test\n\tbackend.PurgeState(task1.UUID)\n\tbackend.PurgeState(task2.UUID)\n\tbackend.PurgeGroupMeta(groupUUID)\n\n\tgroupCompleted, err := backend.GroupCompleted(groupUUID, groupTaskCount)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.InitGroup(groupUUID, []string{task1.UUID, task2.UUID})\n\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, groupTaskCount)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.SetStatePending(task1)\n\tbackend.SetStateStarted(task2)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, groupTaskCount)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\ttaskResults := []*tasks.TaskResult{new(tasks.TaskResult)}\n\tbackend.SetStateSuccess(task1, taskResults)\n\tbackend.SetStateSuccess(task2, taskResults)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, groupTaskCount)\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, groupCompleted)\n\t}\n}\n\nfunc TestGetState(t *testing.T) {\n\tif os.Getenv(\"AMQP_URL\") == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tgo func() {\n\t\tbackend := amqp.New(amqpConfig)\n\t\tbackend.SetStatePending(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\tbackend.SetStateReceived(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\tbackend.SetStateStarted(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\n\t\ttaskResults := []*tasks.TaskResult{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: 2,\n\t\t\t},\n\t\t}\n\t\tbackend.SetStateSuccess(signature, taskResults)\n\t}()\n\n\tbackend := amqp.New(amqpConfig)\n\n\tvar (\n\t\ttaskState *tasks.TaskState\n\t\terr       error\n\t)\n\tfor {\n\t\ttaskState, err = backend.GetState(signature.UUID)\n\t\tif taskState == nil {\n\t\t\tassert.Equal(t, \"No state ready\", err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.NoError(t, err)\n\t\tif taskState.IsCompleted() {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc TestPurgeState(t *testing.T) {\n\tif os.Getenv(\"AMQP_URL\") == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := amqp.New(amqpConfig)\n\n\tbackend.SetStatePending(signature)\n\tbackend.SetStateReceived(signature)\n\ttaskState, err := backend.GetState(signature.UUID)\n\tassert.NotNil(t, taskState)\n\tassert.NoError(t, err)\n\n\tbackend.PurgeState(taskState.TaskUUID)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Nil(t, taskState)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "v2/backends/dynamodb/dynamodb.go",
    "content": "package dynamodb\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\tdynamodbiface \"github.com/RichardKnop/machinery/v2/backends/iface/dynamodb\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tawsconfig \"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue\"\n\t\"github.com/aws/aws-sdk-go-v2/service/dynamodb\"\n\t\"github.com/aws/aws-sdk-go-v2/service/dynamodb/types\"\n)\n\nconst (\n\tBatchItemsLimit  = 99\n\tMaxFetchAttempts = 3\n)\n\n// Backend ...\ntype Backend struct {\n\tcommon.Backend\n\tcnf    *config.Config\n\tclient dynamodbiface.API\n}\n\n// New creates a Backend instance\nfunc New(cnf *config.Config) (iface.Backend, error) {\n\tbackend := &Backend{Backend: common.NewBackend(cnf), cnf: cnf}\n\n\tif cnf.DynamoDB != nil && cnf.DynamoDB.Client != nil {\n\t\tbackend.client = cnf.DynamoDB.Client\n\t} else {\n\t\tcfg, err := awsconfig.LoadDefaultConfig(context.TODO())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: unable to load AWS SDK config: \", err)\n\t\t}\n\n\t\tbackend.client = dynamodb.NewFromConfig(cfg)\n\t}\n\n\t// Check if needed tables exist\n\terr := backend.checkRequiredTablesIfExist()\n\tif err != nil {\n\t\tlog.FATAL.Printf(\"Failed to prepare tables. Error: %v\", err)\n\t}\n\n\treturn backend, nil\n}\n\n// InitGroup ...\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tmeta := tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t\tTTL:       b.getExpirationTime(),\n\t}\n\tav, err := attributevalue.MarshalMap(meta)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Error when marshaling Dynamodb attributes. Err: %v\", err)\n\t\treturn err\n\t}\n\tinput := &dynamodb.PutItemInput{\n\t\tItem:      av,\n\t\tTableName: aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t}\n\t_, err = b.client.PutItem(context.TODO(), input)\n\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Got error when calling PutItem: %v; Error: %v\", input, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// GroupCompleted ...\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(groupMeta.TaskUUIDs)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates ...\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn b.getStates(groupMeta.TaskUUIDs)\n}\n\n// TriggerChord ...\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\t// Get the group meta data\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Chord has already been triggered, return false (should not trigger again)\n\tif groupMeta.ChordTriggered {\n\t\treturn false, nil\n\t}\n\n\t// If group meta is locked, wait until it's unlocked\n\tfor groupMeta.Lock {\n\t\tgroupMeta, _ = b.getGroupMeta(groupUUID)\n\t\tlog.WARNING.Printf(\"Group [%s] locked, waiting\", groupUUID)\n\t\ttime.Sleep(time.Millisecond * 5)\n\t}\n\n\t// Acquire lock\n\tif err = b.lockGroupMeta(groupUUID); err != nil {\n\t\treturn false, err\n\t}\n\tdefer b.unlockGroupMeta(groupUUID)\n\n\t// update group meta data\n\terr = b.chordTriggered(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn true, err\n}\n\n// SetStatePending ...\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\ttaskState := tasks.NewPendingTaskState(signature)\n\t// taskUUID is the primary key of the table, so a new task need to be created first, instead of using dynamodb.UpdateItemInput directly\n\treturn b.initTaskState(taskState)\n}\n\n// SetStateReceived ...\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\treturn b.setTaskState(taskState)\n}\n\n// SetStateStarted ...\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\ttaskState := tasks.NewStartedTaskState(signature)\n\treturn b.setTaskState(taskState)\n}\n\n// SetStateRetry ...\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\ttaskState := tasks.NewRetryTaskState(signature)\n\treturn b.setTaskState(taskState)\n}\n\n// SetStateSuccess ...\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\ttaskState.TTL = b.getExpirationTime()\n\treturn b.setTaskState(taskState)\n}\n\n// SetStateFailure ...\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\ttaskState.TTL = b.getExpirationTime()\n\treturn b.updateToFailureStateWithError(taskState)\n}\n\n// GetState ...\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\tresult, err := b.client.GetItem(context.TODO(), &dynamodb.GetItemInput{\n\t\tTableName: aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t\tKey: map[string]types.AttributeValue{\n\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: taskUUID,\n\t\t\t},\n\t\t},\n\t\tConsistentRead: aws.Bool(true),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn b.unmarshalTaskStateGetItemResult(result)\n}\n\n// getStates returns the current states for the given list of tasks.\n// It uses batch fetch API. If any keys fail to fetch, it'll retry with exponential backoff until maxFetchAttempts times.\nfunc (b *Backend) getStates(tasksToFetch []string) ([]*tasks.TaskState, error) {\n\tfetchedTaskStates := make([]*tasks.TaskState, 0, len(tasksToFetch))\n\tvar unfetchedTaskIDs []string\n\n\t// try until all keys are fetched or until we run out of attempts.\n\tfor attempt := 0; len(tasksToFetch) > 0 && attempt < MaxFetchAttempts; attempt++ {\n\t\tunfetchedTaskIDs = nil\n\t\tfor _, batch := range chunkTasks(tasksToFetch, BatchItemsLimit) {\n\t\t\tfetched, unfetched, err := b.batchFetchTaskStates(batch)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfetchedTaskStates = append(fetchedTaskStates, fetched...)\n\t\t\tunfetchedTaskIDs = append(unfetchedTaskIDs, unfetched...)\n\t\t}\n\t\ttasksToFetch = unfetchedTaskIDs\n\n\t\t// Check if there were any tasks that were not fetched. If so, retry with exponential backoff.\n\t\tif len(unfetchedTaskIDs) > 0 {\n\t\t\tbackoffDuration := time.Duration(math.Pow(2, float64(attempt))) * time.Second\n\t\t\tlog.DEBUG.Printf(\"Unable to fetch [%d] keys on attempt [%d]. Sleeping for [%s]\", len(unfetchedTaskIDs), attempt+1, backoffDuration)\n\t\t\ttime.Sleep(backoffDuration)\n\t\t}\n\t}\n\n\tif len(unfetchedTaskIDs) > 0 {\n\t\treturn nil, fmt.Errorf(\"Failed to fetch [%d] keys even after retries: [%+v]\", len(unfetchedTaskIDs), unfetchedTaskIDs)\n\t}\n\n\treturn fetchedTaskStates, nil\n}\n\n// batchFetchTaskStates returns the current states of the given tasks by fetching them all in a single batched API.\n// DynamoDB's BatchGetItem() can return partial results. If there are any unfetched keys, they are returned as second\n// return value so that the caller can retry those keys.\n// https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/#DynamoDB.BatchGetItem\nfunc (b *Backend) batchFetchTaskStates(taskUUIDs []string) ([]*tasks.TaskState, []string, error) {\n\ttableName := b.cnf.DynamoDB.TaskStatesTable\n\tkeys := make([]map[string]types.AttributeValue, len(taskUUIDs))\n\tfor i, tid := range taskUUIDs {\n\t\tkeys[i] = map[string]types.AttributeValue{\n\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: tid,\n\t\t\t},\n\t\t}\n\t}\n\n\tinput := &dynamodb.BatchGetItemInput{\n\t\tRequestItems: map[string]types.KeysAndAttributes{\n\t\t\ttableName: {\n\t\t\t\tConsistentRead: aws.Bool(true),\n\t\t\t\tKeys:           keys,\n\t\t\t},\n\t\t},\n\t}\n\n\tresult, err := b.client.BatchGetItem(context.TODO(), input)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"BatchGetItem failed. Error: [%s]\", err)\n\t}\n\n\tfetchedKeys, ok := result.Responses[tableName]\n\tif !ok {\n\t\treturn nil, nil, fmt.Errorf(\"no keys returned from the table: [%s]\", tableName)\n\t}\n\n\tstates := []*tasks.TaskState{}\n\tif err := attributevalue.UnmarshalListOfMaps(fetchedKeys, &states); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"Got error when unmarshal map. Error: %v\", err)\n\t}\n\n\t// Look for any unprocessed keys\n\tvar unfetchedKeys []string\n\tif _, ok = result.UnprocessedKeys[tableName]; !ok {\n\t\tunfetchedKeys, err = getUnfetchedKeys(result.UnprocessedKeys[tableName])\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"unable to fetch some keys: [%+v]. Error: [%s]\", result.UnprocessedKeys, err)\n\t\t}\n\t}\n\n\treturn states, unfetchedKeys, nil\n}\n\n// PurgeState ...\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\tinput := &dynamodb.DeleteItemInput{\n\t\tKey: map[string]types.AttributeValue{\n\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: taskUUID,\n\t\t\t},\n\t\t},\n\t\tTableName: aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t}\n\t_, err := b.client.DeleteItem(context.TODO(), input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// PurgeGroupMeta ...\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\tinput := &dynamodb.DeleteItemInput{\n\t\tKey: map[string]types.AttributeValue{\n\t\t\t\"GroupUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: groupUUID,\n\t\t\t},\n\t\t},\n\t\tTableName: aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t}\n\t_, err := b.client.DeleteItem(context.TODO(), input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) getGroupMeta(groupUUID string) (*tasks.GroupMeta, error) {\n\tresult, err := b.client.GetItem(context.TODO(), &dynamodb.GetItemInput{\n\t\tTableName: aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t\tKey: map[string]types.AttributeValue{\n\t\t\t\"GroupUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: groupUUID,\n\t\t\t},\n\t\t},\n\t\tConsistentRead: aws.Bool(true),\n\t})\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Error when getting group [%s]. Error: [%s]\", groupUUID, err)\n\t\treturn nil, err\n\t}\n\titem, err := b.unmarshalGroupMetaGetItemResult(result)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Failed to unmarshal item. Error: [%s], Result: [%+v]\", err, result)\n\t\treturn nil, err\n\t}\n\treturn item, nil\n}\n\nfunc (b *Backend) lockGroupMeta(groupUUID string) error {\n\terr := b.updateGroupMetaLock(groupUUID, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) unlockGroupMeta(groupUUID string) error {\n\terr := b.updateGroupMetaLock(groupUUID, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) updateGroupMetaLock(groupUUID string, status bool) error {\n\tinput := &dynamodb.UpdateItemInput{\n\t\tExpressionAttributeNames: map[string]string{\n\t\t\t\"#L\": \"Lock\",\n\t\t},\n\t\tExpressionAttributeValues: map[string]types.AttributeValue{\n\t\t\t\":l\": &types.AttributeValueMemberBOOL{\n\t\t\t\tValue: status,\n\t\t\t},\n\t\t},\n\t\tKey: map[string]types.AttributeValue{\n\t\t\t\"GroupUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: groupUUID,\n\t\t\t},\n\t\t},\n\t\tReturnValues:     types.ReturnValueUpdatedNew,\n\t\tTableName:        aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t\tUpdateExpression: aws.String(\"SET #L = :l\"),\n\t}\n\n\t_, err := b.client.UpdateItem(context.TODO(), input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) chordTriggered(groupUUID string) error {\n\tinput := &dynamodb.UpdateItemInput{\n\t\tExpressionAttributeNames: map[string]string{\n\t\t\t\"#CT\": \"ChordTriggered\",\n\t\t},\n\t\tExpressionAttributeValues: map[string]types.AttributeValue{\n\t\t\t\":ct\": &types.AttributeValueMemberBOOL{\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tKey: map[string]types.AttributeValue{\n\t\t\t\"GroupUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: groupUUID,\n\t\t\t},\n\t\t},\n\t\tReturnValues:     types.ReturnValueUpdatedNew,\n\t\tTableName:        aws.String(b.cnf.DynamoDB.GroupMetasTable),\n\t\tUpdateExpression: aws.String(\"SET #CT = :ct\"),\n\t}\n\n\t_, err := b.client.UpdateItem(context.TODO(), input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) setTaskState(taskState *tasks.TaskState) error {\n\texpAttributeNames := map[string]string{\n\t\t\"#S\": \"State\",\n\t}\n\texpAttributeValues := map[string]types.AttributeValue{\n\t\t\":s\": &types.AttributeValueMemberS{\n\t\t\tValue: taskState.State,\n\t\t},\n\t}\n\tkeyAttributeValues := map[string]types.AttributeValue{\n\t\t\"TaskUUID\": &types.AttributeValueMemberS{\n\t\t\tValue: taskState.TaskUUID,\n\t\t},\n\t}\n\texp := \"SET #S = :s\"\n\tif !taskState.CreatedAt.IsZero() {\n\t\texpAttributeNames[\"#C\"] = \"CreatedAt\"\n\t\texpAttributeValues[\":c\"] = &types.AttributeValueMemberS{\n\t\t\tValue: taskState.CreatedAt.String(),\n\t\t}\n\t\texp += \", #C = :c\"\n\t}\n\tif taskState.TTL > 0 {\n\t\texpAttributeNames[\"#T\"] = \"TTL\"\n\t\texpAttributeValues[\":t\"] = &types.AttributeValueMemberN{\n\t\t\tValue: fmt.Sprintf(\"%d\", taskState.TTL),\n\t\t}\n\t\texp += \", #T = :t\"\n\t}\n\tif taskState.Results != nil && len(taskState.Results) != 0 {\n\t\texpAttributeNames[\"#R\"] = \"Results\"\n\t\tvar results []types.AttributeValue\n\t\tfor _, r := range taskState.Results {\n\t\t\tavMap := map[string]types.AttributeValue{\n\t\t\t\t\"Type\": &types.AttributeValueMemberS{\n\t\t\t\t\tValue: r.Type,\n\t\t\t\t},\n\t\t\t\t\"Value\": &types.AttributeValueMemberS{\n\t\t\t\t\tValue: fmt.Sprintf(\"%v\", r.Value),\n\t\t\t\t},\n\t\t\t}\n\t\t\trs := &types.AttributeValueMemberM{\n\t\t\t\tValue: avMap,\n\t\t\t}\n\t\t\tresults = append(results, rs)\n\t\t}\n\t\texpAttributeValues[\":r\"] = &types.AttributeValueMemberL{\n\t\t\tValue: results,\n\t\t}\n\t\texp += \", #R = :r\"\n\t}\n\tinput := &dynamodb.UpdateItemInput{\n\t\tExpressionAttributeNames:  expAttributeNames,\n\t\tExpressionAttributeValues: expAttributeValues,\n\t\tKey:                       keyAttributeValues,\n\t\tReturnValues:              types.ReturnValueUpdatedNew,\n\t\tTableName:                 aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t\tUpdateExpression:          aws.String(exp),\n\t}\n\n\t_, err := b.client.UpdateItem(context.TODO(), input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) initTaskState(taskState *tasks.TaskState) error {\n\tav, err := attributevalue.MarshalMap(taskState)\n\tinput := &dynamodb.PutItemInput{\n\t\tItem:      av,\n\t\tTableName: aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = b.client.PutItem(context.TODO(), input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) updateToFailureStateWithError(taskState *tasks.TaskState) error {\n\tinput := &dynamodb.UpdateItemInput{\n\t\tExpressionAttributeNames: map[string]string{\n\t\t\t\"#S\": \"State\",\n\t\t\t\"#E\": \"Error\",\n\t\t},\n\t\tExpressionAttributeValues: map[string]types.AttributeValue{\n\t\t\t\":s\": &types.AttributeValueMemberS{\n\t\t\t\tValue: taskState.State,\n\t\t\t},\n\t\t\t\":e\": &types.AttributeValueMemberS{\n\t\t\t\tValue: taskState.Error,\n\t\t\t},\n\t\t},\n\t\tKey: map[string]types.AttributeValue{\n\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: taskState.TaskUUID,\n\t\t\t},\n\t\t},\n\t\tReturnValues:     types.ReturnValueUpdatedNew,\n\t\tTableName:        aws.String(b.cnf.DynamoDB.TaskStatesTable),\n\t\tUpdateExpression: aws.String(\"SET #S = :s, #E = :e\"),\n\t}\n\n\tif taskState.TTL > 0 {\n\t\tinput.ExpressionAttributeNames[\"#T\"] = \"TTL\"\n\t\tinput.ExpressionAttributeValues[\":t\"] = &types.AttributeValueMemberN{\n\t\t\tValue: fmt.Sprintf(\"%d\", taskState.TTL),\n\t\t}\n\t\tinput.UpdateExpression = aws.String(*input.UpdateExpression + \", #T = :t\")\n\t}\n\n\t_, err := b.client.UpdateItem(context.TODO(), input)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) unmarshalGroupMetaGetItemResult(result *dynamodb.GetItemOutput) (*tasks.GroupMeta, error) {\n\tif result == nil {\n\t\terr := errors.New(\"task state is nil\")\n\t\tlog.ERROR.Printf(\"Got error when unmarshal map. Error: %v\", err)\n\t\treturn nil, err\n\t}\n\titem := tasks.GroupMeta{}\n\terr := attributevalue.UnmarshalMap(result.Item, &item)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Got error when unmarshal map. Error: %v\", err)\n\t\treturn nil, err\n\t}\n\treturn &item, err\n}\n\nfunc (b *Backend) unmarshalTaskStateGetItemResult(result *dynamodb.GetItemOutput) (*tasks.TaskState, error) {\n\tif result == nil {\n\t\terr := errors.New(\"task state is nil\")\n\t\tlog.ERROR.Printf(\"Got error when unmarshal map. Error: %v\", err)\n\t\treturn nil, err\n\t}\n\tstate := tasks.TaskState{}\n\terr := attributevalue.UnmarshalMap(result.Item, &state)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Got error when unmarshal map. Error: %v\", err)\n\t\treturn nil, err\n\t}\n\treturn &state, nil\n}\n\nfunc (b *Backend) checkRequiredTablesIfExist() error {\n\tvar (\n\t\ttaskTableName  = b.cnf.DynamoDB.TaskStatesTable\n\t\tgroupTableName = b.cnf.DynamoDB.GroupMetasTable\n\t\ttableNames     []string\n\t\tstartFromTable *string\n\t)\n\tfor {\n\t\tresult, err := b.client.ListTables(context.TODO(), &dynamodb.ListTablesInput{\n\t\t\tExclusiveStartTableName: startFromTable,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttableNames = append(tableNames, result.TableNames...)\n\t\tif result.LastEvaluatedTableName == nil {\n\t\t\tbreak\n\t\t}\n\t\tstartFromTable = result.LastEvaluatedTableName\n\t}\n\n\tif !b.tableExists(taskTableName, tableNames) {\n\t\treturn errors.New(\"task table doesn't exist\")\n\t}\n\tif !b.tableExists(groupTableName, tableNames) {\n\t\treturn errors.New(\"group table doesn't exist\")\n\t}\n\treturn nil\n}\n\nfunc (b *Backend) tableExists(tableName string, tableNames []string) bool {\n\tfor _, t := range tableNames {\n\t\tif tableName == t {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (b *Backend) getExpirationTime() int64 {\n\texpiresIn := b.GetConfig().ResultsExpireIn\n\tif expiresIn == 0 {\n\t\t// expire results after 1 hour by default\n\t\texpiresIn = config.DefaultResultsExpireIn\n\t}\n\treturn time.Now().Add(time.Second * time.Duration(expiresIn)).Unix()\n}\n\n// getUnfetchedKeys returns keys that were not fetched in a batch request.\nfunc getUnfetchedKeys(unprocessed types.KeysAndAttributes) ([]string, error) {\n\tstates := []*tasks.TaskState{}\n\tvar taskIDs []string\n\tif err := attributevalue.UnmarshalListOfMaps(unprocessed.Keys, &states); err != nil {\n\t\treturn nil, fmt.Errorf(\"Got error when unmarshal map. Error: %v\", err)\n\t}\n\tfor _, s := range states {\n\t\ttaskIDs = append(taskIDs, s.TaskUUID)\n\t}\n\treturn taskIDs, nil\n}\n\n// chunkTasks chunks the list of strings into multiple smaller lists of specified size.\nfunc chunkTasks(array []string, chunkSize int) [][]string {\n\tvar result [][]string\n\tfor len(array) > 0 {\n\t\tsz := min(len(array), chunkSize)\n\t\tchunk := array[:sz]\n\t\tarray = array[sz:]\n\t\tresult = append(result, chunk)\n\t}\n\treturn result\n}\n\nfunc min(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "v2/backends/dynamodb/dynamodb_export_test.go",
    "content": "package dynamodb\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\n\tdynamodbiface \"github.com/RichardKnop/machinery/v2/backends/iface/dynamodb\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/aws/aws-sdk-go-v2/service/dynamodb\"\n\t\"github.com/aws/aws-sdk-go-v2/service/dynamodb/types\"\n)\n\nvar (\n\tTestDynamoDBBackend    *Backend\n\tTestErrDynamoDBBackend *Backend\n\tTestCnf                *config.Config\n\tTestDBClient           dynamodbiface.API\n\tTestErrDBClient        dynamodbiface.API\n\tTestGroupMeta          *tasks.GroupMeta\n\tTestTask1              map[string]types.AttributeValue\n\tTestTask2              map[string]types.AttributeValue\n\tTestTask3              map[string]types.AttributeValue\n)\n\ntype TestDynamoDBClient struct {\n\tdynamodbiface.API\n\tPutItemOverride      func(context.Context, *dynamodb.PutItemInput, ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error)\n\tUpdateItemOverride   func(context.Context, *dynamodb.UpdateItemInput, ...func(*dynamodb.Options)) (*dynamodb.UpdateItemOutput, error)\n\tGetItemOverride      func(ctx context.Context, input *dynamodb.GetItemInput, ops ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error)\n\tBatchGetItemOverride func(context.Context, *dynamodb.BatchGetItemInput, ...func(*dynamodb.Options)) (*dynamodb.BatchGetItemOutput, error)\n}\n\nfunc (t *TestDynamoDBClient) ResetOverrides() {\n\tt.PutItemOverride = nil\n\tt.UpdateItemOverride = nil\n\tt.BatchGetItemOverride = nil\n}\n\nfunc (t *TestDynamoDBClient) PutItem(ctx context.Context, input *dynamodb.PutItemInput, ops ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error) {\n\tif t.PutItemOverride != nil {\n\t\treturn t.PutItemOverride(ctx, input, ops...)\n\t}\n\treturn &dynamodb.PutItemOutput{}, nil\n}\nfunc (t *TestDynamoDBClient) BatchGetItem(ctx context.Context, input *dynamodb.BatchGetItemInput, ops ...func(*dynamodb.Options)) (*dynamodb.BatchGetItemOutput, error) {\n\tif t.BatchGetItemOverride != nil {\n\t\treturn t.BatchGetItemOverride(ctx, input, ops...)\n\t}\n\treturn &dynamodb.BatchGetItemOutput{}, nil\n}\n\nfunc (t *TestDynamoDBClient) GetItem(ctx context.Context, input *dynamodb.GetItemInput, ops ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error) {\n\tif t.GetItemOverride != nil {\n\t\treturn t.GetItemOverride(ctx, input, ops...)\n\t}\n\tvar output *dynamodb.GetItemOutput\n\tswitch *input.TableName {\n\tcase \"group_metas\":\n\t\toutput = &dynamodb.GetItemOutput{\n\t\t\tItem: map[string]types.AttributeValue{\n\t\t\t\t\"TaskUUIDs\": &types.AttributeValueMemberL{\n\t\t\t\t\tValue: []types.AttributeValue{\n\t\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\t\tValue: \"testTaskUUID1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\t\tValue: \"testTaskUUID2\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\t\tValue: \"testTaskUUID3\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"ChordTriggered\": &types.AttributeValueMemberBOOL{\n\t\t\t\t\tValue: false,\n\t\t\t\t},\n\t\t\t\t\"GroupUUID\": &types.AttributeValueMemberS{\n\t\t\t\t\tValue: \"testGroupUUID\",\n\t\t\t\t},\n\t\t\t\t\"Lock\": &types.AttributeValueMemberBOOL{\n\t\t\t\t\tValue: false,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\tcase \"task_states\":\n\t\tif input.Key[\"TaskUUID\"] == nil {\n\t\t\toutput = &dynamodb.GetItemOutput{\n\t\t\t\tItem: map[string]types.AttributeValue{\n\t\t\t\t\t\"Error\": &types.AttributeValueMemberNULL{\n\t\t\t\t\t\tValue: false,\n\t\t\t\t\t},\n\t\t\t\t\t\"State\": &types.AttributeValueMemberS{\n\t\t\t\t\t\tValue: tasks.StatePending,\n\t\t\t\t\t},\n\t\t\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{\n\t\t\t\t\t\tValue: \"testTaskUUID1\",\n\t\t\t\t\t},\n\t\t\t\t\t\"Results:\": &types.AttributeValueMemberNULL{\n\t\t\t\t\t\tValue: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\tif input.Key[\"TaskUUID\"].(*types.AttributeValueMemberS).Value == \"testTaskUUID1\" {\n\t\t\t\toutput = &dynamodb.GetItemOutput{\n\t\t\t\t\tItem: TestTask1,\n\t\t\t\t}\n\t\t\t} else if input.Key[\"TaskUUID\"].(*types.AttributeValueMemberS).Value == \"testTaskUUID2\" {\n\t\t\t\toutput = &dynamodb.GetItemOutput{\n\t\t\t\t\tItem: TestTask2,\n\t\t\t\t}\n\n\t\t\t} else if input.Key[\"TaskUUID\"].(*types.AttributeValueMemberS).Value == \"testTaskUUID3\" {\n\t\t\t\toutput = &dynamodb.GetItemOutput{\n\t\t\t\t\tItem: TestTask3,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\treturn output, nil\n}\n\nfunc (t *TestDynamoDBClient) DeleteItem(ctx context.Context, input *dynamodb.DeleteItemInput, ops ...func(*dynamodb.Options)) (*dynamodb.DeleteItemOutput, error) {\n\treturn &dynamodb.DeleteItemOutput{}, nil\n}\n\nfunc (t *TestDynamoDBClient) UpdateItem(ctx context.Context, input *dynamodb.UpdateItemInput, ops ...func(*dynamodb.Options)) (*dynamodb.UpdateItemOutput, error) {\n\tif t.UpdateItemOverride != nil {\n\t\treturn t.UpdateItemOverride(ctx, input, ops...)\n\t}\n\treturn &dynamodb.UpdateItemOutput{}, nil\n}\n\nfunc (t *TestDynamoDBClient) ListTables(ctx context.Context, input *dynamodb.ListTablesInput, ops ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error) {\n\treturn &dynamodb.ListTablesOutput{\n\t\tTableNames: []string{\n\t\t\t\"group_metas\",\n\t\t\t\"task_states\",\n\t\t},\n\t}, nil\n}\n\n// Always returns error\ntype TestErrDynamoDBClient struct {\n\tdynamodbiface.API\n}\n\nfunc (t *TestErrDynamoDBClient) PutItem(context.Context, *dynamodb.PutItemInput, ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error) {\n\treturn nil, errors.New(\"error when putting an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) GetItem(context.Context, *dynamodb.GetItemInput, ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error) {\n\treturn nil, errors.New(\"error when getting an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) DeleteItem(context.Context, *dynamodb.DeleteItemInput, ...func(*dynamodb.Options)) (*dynamodb.DeleteItemOutput, error) {\n\treturn nil, errors.New(\"error when deleting an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) Scan(context.Context, *dynamodb.ScanInput, ...func(*dynamodb.Options)) (*dynamodb.ScanOutput, error) {\n\treturn nil, errors.New(\"error when scanning an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) UpdateItem(context.Context, *dynamodb.UpdateItemInput, ...func(*dynamodb.Options)) (*dynamodb.UpdateItemOutput, error) {\n\treturn nil, errors.New(\"error when updating an item\")\n}\n\nfunc (t *TestErrDynamoDBClient) ListTables(context.Context, *dynamodb.ListTablesInput, ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error) {\n\treturn nil, errors.New(\"error when listing tables\")\n}\n\nfunc init() {\n\tTestCnf = &config.Config{\n\t\tResultBackend:   os.Getenv(\"DYNAMODB_URL\"),\n\t\tResultsExpireIn: 30,\n\t\tDynamoDB: &config.DynamoDBConfig{\n\t\t\tTaskStatesTable: \"task_states\",\n\t\t\tGroupMetasTable: \"group_metas\",\n\t\t},\n\t}\n\tTestDBClient = new(TestDynamoDBClient)\n\tTestDynamoDBBackend = &Backend{cnf: TestCnf, client: TestDBClient}\n\n\tTestErrDBClient = new(TestErrDynamoDBClient)\n\tTestErrDynamoDBBackend = &Backend{cnf: TestCnf, client: TestErrDBClient}\n\n\tTestGroupMeta = &tasks.GroupMeta{\n\t\tGroupUUID: \"testGroupUUID\",\n\t\tTaskUUIDs: []string{\"testTaskUUID1\", \"testTaskUUID2\", \"testTaskUUID3\"},\n\t}\n}\n\nfunc (b *Backend) GetConfig() *config.Config {\n\treturn b.cnf\n}\n\nfunc (b *Backend) GetClient() dynamodbiface.API {\n\treturn b.client\n}\n\nfunc (b *Backend) GetGroupMetaForTest(groupUUID string) (*tasks.GroupMeta, error) {\n\treturn b.getGroupMeta(groupUUID)\n}\n\nfunc (b *Backend) UnmarshalGroupMetaGetItemResultForTest(result *dynamodb.GetItemOutput) (*tasks.GroupMeta, error) {\n\treturn b.unmarshalGroupMetaGetItemResult(result)\n}\n\nfunc (b *Backend) UnmarshalTaskStateGetItemResultForTest(result *dynamodb.GetItemOutput) (*tasks.TaskState, error) {\n\treturn b.unmarshalTaskStateGetItemResult(result)\n}\n\nfunc (b *Backend) SetTaskStateForTest(taskState *tasks.TaskState) error {\n\treturn b.setTaskState(taskState)\n}\n\nfunc (b *Backend) ChordTriggeredForTest(groupUUID string) error {\n\treturn b.chordTriggered(groupUUID)\n}\n\nfunc (b *Backend) UpdateGroupMetaLockForTest(groupUUID string, status bool) error {\n\treturn b.updateGroupMetaLock(groupUUID, status)\n}\n\nfunc (b *Backend) UnlockGroupMetaForTest(groupUUID string) error {\n\treturn b.unlockGroupMeta(groupUUID)\n}\n\nfunc (b *Backend) LockGroupMetaForTest(groupUUID string) error {\n\treturn b.lockGroupMeta(groupUUID)\n}\n\nfunc (b *Backend) GetStatesForTest(taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\treturn b.getStates(taskUUIDs)\n}\n\nfunc (b *Backend) UpdateToFailureStateWithErrorForTest(taskState *tasks.TaskState) error {\n\treturn b.updateToFailureStateWithError(taskState)\n}\n\nfunc (b *Backend) TableExistsForTest(tableName string, tableNames []string) bool {\n\treturn b.tableExists(tableName, tableNames)\n}\n\nfunc (b *Backend) CheckRequiredTablesIfExistForTest() error {\n\treturn b.checkRequiredTablesIfExist()\n}\n"
  },
  {
    "path": "v2/backends/dynamodb/dynamodb_test.go",
    "content": "package dynamodb_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/dynamodb\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\tawsdynamodb \"github.com/aws/aws-sdk-go-v2/service/dynamodb\"\n\t\"github.com/aws/aws-sdk-go-v2/service/dynamodb/types\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNew(t *testing.T) {\n\t// should call t.Skip if not connected to internet\n\tbackend, err := dynamodb.New(dynamodb.TestCnf)\n\trequire.NoError(t, err)\n\tassert.IsType(t, new(dynamodb.Backend), backend)\n}\n\nfunc TestInitGroup(t *testing.T) {\n\tgroupUUID := \"testGroupUUID\"\n\ttaskUUIDs := []string{\"testTaskUUID1\", \"testTaskUUID2\", \"testTaskUUID3\"}\n\tlog.INFO.Println(dynamodb.TestDynamoDBBackend.GetConfig())\n\n\terr := dynamodb.TestDynamoDBBackend.InitGroup(groupUUID, taskUUIDs)\n\tassert.Nil(t, err)\n\n\terr = dynamodb.TestErrDynamoDBBackend.InitGroup(groupUUID, taskUUIDs)\n\tassert.NotNil(t, err)\n\n\t// assert proper TTL value is set in InitGroup()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 3 * 3600 // results should expire after 3 hours\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\t// Override DynamoDB PutItem() behavior\n\tvar isPutItemCalled bool\n\tclient.PutItemOverride = func(ctx context.Context, input *awsdynamodb.PutItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.PutItemOutput, error) {\n\t\tisPutItemCalled = true\n\t\tassert.NotNil(t, input)\n\n\t\tactualTTLStr := input.Item[\"TTL\"].(*types.AttributeValueMemberN).Value\n\t\texpectedTTLTime := time.Now().Add(3 * time.Hour)\n\t\tassertTTLValue(t, expectedTTLTime, actualTTLStr)\n\n\t\treturn &awsdynamodb.PutItemOutput{}, nil\n\t}\n\terr = dynamodb.TestDynamoDBBackend.InitGroup(groupUUID, taskUUIDs)\n\tassert.Nil(t, err)\n\tassert.True(t, isPutItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc assertTTLValue(t *testing.T, expectedTTLTime time.Time, actualEncodedTTLValue string) {\n\tactualTTLTimestamp, err := strconv.ParseInt(actualEncodedTTLValue, 10, 64)\n\tassert.Nil(t, err)\n\tactualTTLTime := time.Unix(actualTTLTimestamp, 0)\n\tassert.WithinDuration(t, expectedTTLTime, actualTTLTime, time.Second)\n}\n\nfunc TestGroupCompleted(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\t// Override DynamoDB BatchGetItem() behavior\n\tvar isBatchGetItemCalled bool\n\tclient.BatchGetItemOverride = func(ctx context.Context, input *awsdynamodb.BatchGetItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\tisBatchGetItemCalled = true\n\t\tassert.NotNil(t, input)\n\t\tassert.Nil(t, validateBatchGetItemInput(input))\n\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]types.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateSuccess}},\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateSuccess}},\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateFailure}},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tgroupUUID := \"testGroupUUID\"\n\tisCompleted, err := dynamodb.TestDynamoDBBackend.GroupCompleted(groupUUID, 3)\n\tassert.Nil(t, err)\n\tassert.True(t, isCompleted)\n\tassert.True(t, isBatchGetItemCalled)\n\tclient.ResetOverrides()\n}\nfunc TestGroupCompletedReturnsError(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tclient.BatchGetItemOverride = func(ctx context.Context, input *awsdynamodb.BatchGetItemInput, ops ...func(options *awsdynamodb.Options)) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\treturn nil, fmt.Errorf(\"Simulating error from AWS\")\n\t}\n\tisCompleted, err := dynamodb.TestDynamoDBBackend.GroupCompleted(\"test\", 3)\n\tassert.NotNil(t, err)\n\tassert.False(t, isCompleted)\n\tclient.ResetOverrides()\n}\n\n// TestGroupCompletedReturnsFalse tests that the GroupCompleted() returns false when some tasks have not yet finished.\nfunc TestGroupCompletedReturnsFalse(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\t// Override DynamoDB BatchGetItem() behavior\n\tclient.BatchGetItemOverride = func(ctx context.Context, _ *awsdynamodb.BatchGetItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]types.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateSuccess}},\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateFailure}},\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StatePending}},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tisCompleted, err := dynamodb.TestDynamoDBBackend.GroupCompleted(\"testGroup\", 3)\n\tassert.Nil(t, err)\n\tassert.False(t, isCompleted)\n\tclient.ResetOverrides()\n}\n\n// TestGroupCompletedReturnsFalse tests that the GroupCompleted() retries the the request until MaxFetchAttempts before returning an error\nfunc TestGroupCompletedRetries(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\t// Override DynamoDB BatchGetItem() behavior\n\tvar countBatchGetItemAPICalls int\n\tclient.BatchGetItemOverride = func(ctx context.Context, _ *awsdynamodb.BatchGetItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\tcountBatchGetItemAPICalls++\n\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]types.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateSuccess}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tUnprocessedKeys: map[string]types.KeysAndAttributes{\n\t\t\t\ttableName: {\n\t\t\t\t\tKeys: []map[string]types.AttributeValue{\n\t\t\t\t\t\t{\"TaskUUID\": &types.AttributeValueMemberS{Value: \"unfetchedTaskUUID1\"}},\n\t\t\t\t\t\t{\"TaskUUID\": &types.AttributeValueMemberS{Value: \"unfetchedTaskUUID2\"}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\t_, err := dynamodb.TestDynamoDBBackend.GroupCompleted(\"testGroup\", 3)\n\tassert.NotNil(t, err)\n\tassert.Equal(t, dynamodb.MaxFetchAttempts, countBatchGetItemAPICalls)\n\tclient.ResetOverrides()\n}\n\n// TestGroupCompletedReturnsFalse tests that the GroupCompleted() retries the the request and returns success if all keys are fetched on retries.\nfunc TestGroupCompletedRetrieSuccess(t *testing.T) {\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\t// Override DynamoDB BatchGetItem() behavior\n\tvar countBatchGetItemAPICalls int\n\tclient.BatchGetItemOverride = func(ctx context.Context, _ *awsdynamodb.BatchGetItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\tcountBatchGetItemAPICalls++\n\n\t\t// simulate unfetched keys on 1st attempt.\n\t\tif countBatchGetItemAPICalls == 1 {\n\t\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\t\tResponses: map[string][]map[string]types.AttributeValue{\n\t\t\t\t\ttableName: {}, // no keys returned in this attempt.\n\t\t\t\t},\n\t\t\t\tUnprocessedKeys: map[string]types.KeysAndAttributes{\n\t\t\t\t\ttableName: {\n\t\t\t\t\t\tKeys: []map[string]types.AttributeValue{\n\t\t\t\t\t\t\t{\"TaskUUID\": &types.AttributeValueMemberS{Value: \"unfetchedTaskUUID1\"}},\n\t\t\t\t\t\t\t{\"TaskUUID\": &types.AttributeValueMemberS{Value: \"unfetchedTaskUUID2\"}},\n\t\t\t\t\t\t\t{\"TaskUUID\": &types.AttributeValueMemberS{Value: \"unfetchedTaskUUID3\"}},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, nil\n\n\t\t}\n\n\t\t// Return all keys in subsequent attempts.\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]types.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateSuccess}},\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateSuccess}},\n\t\t\t\t\t{\"State\": &types.AttributeValueMemberS{Value: tasks.StateSuccess}},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tisCompleted, err := dynamodb.TestDynamoDBBackend.GroupCompleted(\"testGroup\", 3)\n\tassert.Nil(t, err)\n\tassert.True(t, isCompleted)\n\tassert.Equal(t, 2, countBatchGetItemAPICalls)\n\tclient.ResetOverrides()\n}\n\nfunc TestPrivateFuncGetGroupMeta(t *testing.T) {\n\tgroupUUID := \"testGroupUUID\"\n\tmeta, err := dynamodb.TestDynamoDBBackend.GetGroupMetaForTest(groupUUID)\n\titem := tasks.GroupMeta{\n\t\tGroupUUID:      \"testGroupUUID\",\n\t\tLock:           false,\n\t\tChordTriggered: false,\n\t\tTaskUUIDs: []string{\n\t\t\t\"testTaskUUID1\",\n\t\t\t\"testTaskUUID2\",\n\t\t\t\"testTaskUUID3\",\n\t\t},\n\t}\n\tassert.Nil(t, err)\n\tassert.EqualValues(t, item, *meta)\n\t_, err = dynamodb.TestErrDynamoDBBackend.GetGroupMetaForTest(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncUnmarshalTaskStateGetItemResult(t *testing.T) {\n\tresult := awsdynamodb.GetItemOutput{\n\t\tItem: map[string]types.AttributeValue{\n\t\t\t\"Error\": &types.AttributeValueMemberNULL{\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t\t\"State\": &types.AttributeValueMemberS{\n\t\t\t\tValue: tasks.StatePending,\n\t\t\t},\n\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: \"testTaskUUID1\",\n\t\t\t},\n\t\t\t\"Results:\": &types.AttributeValueMemberNULL{\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n\n\tinvalidResult := awsdynamodb.GetItemOutput{\n\t\tItem: map[string]types.AttributeValue{\n\t\t\t\"Error\": &types.AttributeValueMemberBOOL{\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t\t\"State\": &types.AttributeValueMemberS{\n\t\t\t\tValue: tasks.StatePending,\n\t\t\t},\n\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: \"testTaskUUID1\",\n\t\t\t},\n\t\t\t\"Results:\": &types.AttributeValueMemberBOOL{\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n\n\titem := tasks.TaskState{\n\t\tTaskUUID: \"testTaskUUID1\",\n\t\tResults:  nil,\n\t\tState:    tasks.StatePending,\n\t\tError:    \"\",\n\t}\n\tstate, err := dynamodb.TestErrDynamoDBBackend.UnmarshalTaskStateGetItemResultForTest(&result)\n\tassert.Nil(t, err)\n\tassert.EqualValues(t, item, *state)\n\n\t_, err = dynamodb.TestDynamoDBBackend.UnmarshalTaskStateGetItemResultForTest(nil)\n\tassert.NotNil(t, err)\n\n\t_, err = dynamodb.TestDynamoDBBackend.UnmarshalTaskStateGetItemResultForTest(&invalidResult)\n\tassert.NotNil(t, err)\n\n}\n\nfunc TestPrivateFuncUnmarshalGroupMetaGetItemResult(t *testing.T) {\n\tresult := awsdynamodb.GetItemOutput{\n\t\tItem: map[string]types.AttributeValue{\n\t\t\t\"TaskUUIDs\": &types.AttributeValueMemberL{\n\t\t\t\tValue: []types.AttributeValue{\n\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\tValue: \"testTaskUUID1\",\n\t\t\t\t\t},\n\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\tValue: \"testTaskUUID2\",\n\t\t\t\t\t},\n\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\tValue: \"testTaskUUID3\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"ChordTriggered\": &types.AttributeValueMemberBOOL{\n\t\t\t\tValue: false,\n\t\t\t},\n\t\t\t\"GroupUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: \"testGroupUUID\",\n\t\t\t},\n\t\t\t\"Lock\": &types.AttributeValueMemberBOOL{\n\t\t\t\tValue: false,\n\t\t\t},\n\t\t},\n\t}\n\n\tinvalidResult := awsdynamodb.GetItemOutput{\n\t\tItem: map[string]types.AttributeValue{\n\t\t\t\"TaskUUIDs\": &types.AttributeValueMemberL{\n\t\t\t\tValue: []types.AttributeValue{\n\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\tValue: \"testTaskUUID1\",\n\t\t\t\t\t},\n\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\tValue: \"testTaskUUID2\",\n\t\t\t\t\t},\n\t\t\t\t\t&types.AttributeValueMemberS{\n\t\t\t\t\t\tValue: \"testTaskUUID3\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"ChordTriggered\": &types.AttributeValueMemberS{\n\t\t\t\tValue: \"false\", // this attribute is invalid\n\t\t\t},\n\t\t\t\"GroupUUID\": &types.AttributeValueMemberS{\n\t\t\t\tValue: \"testGroupUUID\",\n\t\t\t},\n\t\t\t\"Lock\": &types.AttributeValueMemberBOOL{\n\t\t\t\tValue: false,\n\t\t\t},\n\t\t},\n\t}\n\n\titem := tasks.GroupMeta{\n\t\tGroupUUID:      \"testGroupUUID\",\n\t\tLock:           false,\n\t\tChordTriggered: false,\n\t\tTaskUUIDs: []string{\n\t\t\t\"testTaskUUID1\",\n\t\t\t\"testTaskUUID2\",\n\t\t\t\"testTaskUUID3\",\n\t\t},\n\t}\n\tmeta, err := dynamodb.TestErrDynamoDBBackend.UnmarshalGroupMetaGetItemResultForTest(&result)\n\tassert.Nil(t, err)\n\tassert.EqualValues(t, item, *meta)\n\t_, err = dynamodb.TestErrDynamoDBBackend.UnmarshalGroupMetaGetItemResultForTest(nil)\n\tassert.NotNil(t, err)\n\n\t_, err = dynamodb.TestErrDynamoDBBackend.UnmarshalGroupMetaGetItemResultForTest(&invalidResult)\n\tassert.NotNil(t, err)\n\n}\n\nfunc TestPrivateFuncSetTaskState(t *testing.T) {\n\tsignature := &tasks.Signature{\n\t\tName: \"Test\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 1,\n\t\t\t},\n\t\t},\n\t}\n\tstate := tasks.NewPendingTaskState(signature)\n\terr := dynamodb.TestErrDynamoDBBackend.SetTaskStateForTest(state)\n\tassert.NotNil(t, err)\n\terr = dynamodb.TestDynamoDBBackend.SetTaskStateForTest(state)\n\tassert.Nil(t, err)\n}\n\n// verifyUpdateInput is a helper function to verify valid dynamoDB update input.\nfunc verifyUpdateInput(t *testing.T, input *awsdynamodb.UpdateItemInput, expectedTaskID string, expectedState string, expectedTTLTime time.Time) {\n\tassert.NotNil(t, input)\n\n\t// verify task ID\n\tassert.Equal(t, expectedTaskID, input.Key[\"TaskUUID\"].(*types.AttributeValueMemberS).Value)\n\n\t// verify task state\n\tassert.Equal(t, expectedState, input.ExpressionAttributeValues[\":s\"].(*types.AttributeValueMemberS).Value)\n\n\t// Verify TTL\n\tif !expectedTTLTime.IsZero() {\n\t\tactualTTLStr := input.ExpressionAttributeValues[\":t\"].(*types.AttributeValueMemberN).Value\n\t\tassertTTLValue(t, expectedTTLTime, actualTTLStr)\n\t}\n}\n\nfunc TestSetStateSuccess(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and TTL value is set in SetStateSuccess()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 3 * 3600 // results should expire after 3 hours\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\t// Override DynamoDB UpdateItem() behavior\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(ctx context.Context, input *awsdynamodb.UpdateItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateSuccess, time.Now().Add(3*time.Hour))\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateSuccess(signature, nil)\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestSetStateFailure(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and TTL value is set in SetStateFailure()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 2 * 3600 // results should expire after 2 hours\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\t// Override DynamoDB UpdateItem() behavior\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(ctx context.Context, input *awsdynamodb.UpdateItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateFailure, time.Now().Add(2*time.Hour))\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateFailure(signature, \"Some error occurred\")\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestSetStateReceived(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and *no* TTL value is set in SetStateReceived()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 2 * 3600 // results should expire after 2 hours (ignored for this state)\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(ctx context.Context, input *awsdynamodb.UpdateItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateReceived, time.Time{})\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateReceived(signature)\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestSetStateStarted(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and *no* TTL value is set in SetStateStarted()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 2 * 3600 // results should expire after 2 hours (ignored for this state)\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(ctx context.Context, input *awsdynamodb.UpdateItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateStarted, time.Time{})\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateStarted(signature)\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestSetStateRetry(t *testing.T) {\n\tsignature := &tasks.Signature{UUID: \"testTaskUUID\"}\n\n\t// assert correct task ID, state and *no* TTL value is set in SetStateStarted()\n\tdynamodb.TestDynamoDBBackend.GetConfig().ResultsExpireIn = 2 * 3600 // results should expire after 2 hours (ignored for this state)\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tvar isUpdateItemCalled bool\n\tclient.UpdateItemOverride = func(ctx context.Context, input *awsdynamodb.UpdateItemInput, ops ...func(options *awsdynamodb.Options)) (*awsdynamodb.UpdateItemOutput, error) {\n\t\tisUpdateItemCalled = true\n\t\tverifyUpdateInput(t, input, signature.UUID, tasks.StateRetry, time.Time{})\n\t\treturn &awsdynamodb.UpdateItemOutput{}, nil\n\t}\n\n\terr := dynamodb.TestDynamoDBBackend.SetStateRetry(signature)\n\tassert.Nil(t, err)\n\tassert.True(t, isUpdateItemCalled)\n\tclient.ResetOverrides()\n}\n\nfunc TestGroupTaskStates(t *testing.T) {\n\texpectedStates := map[string]*tasks.TaskState{\n\t\t\"testTaskUUID1\": {\n\t\t\tTaskUUID: \"testTaskUUID1\",\n\t\t\tResults:  nil,\n\t\t\tState:    tasks.StatePending,\n\t\t\tError:    \"\",\n\t\t},\n\t\t\"testTaskUUID2\": {\n\t\t\tTaskUUID: \"testTaskUUID2\",\n\t\t\tResults:  nil,\n\t\t\tState:    tasks.StateStarted,\n\t\t\tError:    \"\",\n\t\t},\n\t\t\"testTaskUUID3\": {\n\t\t\tTaskUUID: \"testTaskUUID3\",\n\t\t\tResults:  nil,\n\t\t\tState:    tasks.StateSuccess,\n\t\t\tError:    \"\",\n\t\t},\n\t}\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\ttableName := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\tclient.BatchGetItemOverride = func(ctx context.Context, input *awsdynamodb.BatchGetItemInput, ops ...func(options *awsdynamodb.Options)) (*awsdynamodb.BatchGetItemOutput, error) {\n\t\tassert.Nil(t, validateBatchGetItemInput(input))\n\n\t\treturn &awsdynamodb.BatchGetItemOutput{\n\t\t\tResponses: map[string][]map[string]types.AttributeValue{\n\t\t\t\ttableName: {\n\t\t\t\t\t{\n\t\t\t\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{Value: \"testTaskUUID1\"},\n\t\t\t\t\t\t\"Results:\": &types.AttributeValueMemberNULL{Value: true},\n\t\t\t\t\t\t\"State\":    &types.AttributeValueMemberS{Value: tasks.StatePending},\n\t\t\t\t\t\t\"Error\":    &types.AttributeValueMemberNULL{Value: true},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{Value: \"testTaskUUID2\"},\n\t\t\t\t\t\t\"Results:\": &types.AttributeValueMemberNULL{Value: true},\n\t\t\t\t\t\t\"State\":    &types.AttributeValueMemberS{Value: tasks.StateStarted},\n\t\t\t\t\t\t\"Error\":    &types.AttributeValueMemberNULL{Value: true},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{Value: \"testTaskUUID3\"},\n\t\t\t\t\t\t\"Results:\": &types.AttributeValueMemberNULL{Value: true},\n\t\t\t\t\t\t\"State\":    &types.AttributeValueMemberS{Value: tasks.StateSuccess},\n\t\t\t\t\t\t\"Error\":    &types.AttributeValueMemberNULL{Value: true},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\tdefer client.ResetOverrides()\n\n\tstates, err := dynamodb.TestDynamoDBBackend.GroupTaskStates(\"testGroupUUID\", 3)\n\tassert.Nil(t, err)\n\tfor _, s := range states {\n\t\tassert.EqualValues(t, *s, *expectedStates[s.TaskUUID])\n\t}\n}\n\nfunc TestTriggerChord(t *testing.T) {\n\tgroupUUID := \"testGroupUUID\"\n\ttriggered, err := dynamodb.TestDynamoDBBackend.TriggerChord(groupUUID)\n\tassert.Nil(t, err)\n\tassert.True(t, triggered)\n}\n\nfunc TestGetState(t *testing.T) {\n\ttaskUUID := \"testTaskUUID1\"\n\texpectedState := &tasks.TaskState{\n\t\tTaskUUID: \"testTaskUUID1\",\n\t\tResults:  nil,\n\t\tState:    tasks.StatePending,\n\t\tError:    \"\",\n\t}\n\tclient := dynamodb.TestDynamoDBBackend.GetClient().(*dynamodb.TestDynamoDBClient)\n\tclient.GetItemOverride = func(ctx context.Context, input *awsdynamodb.GetItemInput, ops ...func(*awsdynamodb.Options)) (*awsdynamodb.GetItemOutput, error) {\n\t\treturn &awsdynamodb.GetItemOutput{\n\t\t\tItem: map[string]types.AttributeValue{\n\t\t\t\t\"TaskUUID\": &types.AttributeValueMemberS{Value: \"testTaskUUID1\"},\n\t\t\t\t\"Results:\": &types.AttributeValueMemberNULL{Value: true},\n\t\t\t\t\"State\":    &types.AttributeValueMemberS{Value: tasks.StatePending},\n\t\t\t\t\"Error\":    &types.AttributeValueMemberNULL{Value: false},\n\t\t\t},\n\t\t}, nil\n\t}\n\tdefer client.ResetOverrides()\n\n\tstate, err := dynamodb.TestDynamoDBBackend.GetState(taskUUID)\n\tassert.Nil(t, err)\n\tassert.EqualValues(t, expectedState, state)\n}\n\nfunc TestPurgeState(t *testing.T) {\n\ttaskUUID := \"testTaskUUID1\"\n\terr := dynamodb.TestDynamoDBBackend.PurgeState(taskUUID)\n\tassert.Nil(t, err)\n\n\terr = dynamodb.TestErrDynamoDBBackend.PurgeState(taskUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPurgeGroupMeta(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.PurgeGroupMeta(groupUUID)\n\tassert.Nil(t, err)\n\n\terr = dynamodb.TestErrDynamoDBBackend.PurgeGroupMeta(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncLockGroupMeta(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.LockGroupMetaForTest(groupUUID)\n\tassert.Nil(t, err)\n\terr = dynamodb.TestErrDynamoDBBackend.LockGroupMetaForTest(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncUnLockGroupMeta(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.UnlockGroupMetaForTest(groupUUID)\n\tassert.Nil(t, err)\n\terr = dynamodb.TestErrDynamoDBBackend.UnlockGroupMetaForTest(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncChordTriggered(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.ChordTriggeredForTest(groupUUID)\n\tassert.Nil(t, err)\n\terr = dynamodb.TestErrDynamoDBBackend.ChordTriggeredForTest(groupUUID)\n\tassert.NotNil(t, err)\n}\n\nfunc TestDynamoDBPrivateFuncUpdateGroupMetaLock(t *testing.T) {\n\tgroupUUID := \"GroupUUID\"\n\terr := dynamodb.TestDynamoDBBackend.UpdateGroupMetaLockForTest(groupUUID, true)\n\tassert.Nil(t, err)\n\terr = dynamodb.TestErrDynamoDBBackend.UpdateGroupMetaLockForTest(groupUUID, true)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFuncUpdateToFailureStateWithError(t *testing.T) {\n\tsignature := &tasks.Signature{\n\t\tName: \"Test\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: 1,\n\t\t\t},\n\t\t},\n\t}\n\n\tstate := tasks.NewFailureTaskState(signature, \"This is an error\")\n\terr := dynamodb.TestDynamoDBBackend.UpdateToFailureStateWithErrorForTest(state)\n\tassert.Nil(t, err)\n}\n\nfunc TestPrivateFuncTableExistsForTest(t *testing.T) {\n\ttables := []string{\"foo\"}\n\tassert.False(t, dynamodb.TestDynamoDBBackend.TableExistsForTest(\"bar\", tables))\n\tassert.True(t, dynamodb.TestDynamoDBBackend.TableExistsForTest(\"foo\", tables))\n}\n\nfunc TestPrivateFuncCheckRequiredTablesIfExistForTest(t *testing.T) {\n\terr := dynamodb.TestDynamoDBBackend.CheckRequiredTablesIfExistForTest()\n\tassert.Nil(t, err)\n\ttaskTable := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable\n\tgroupTable := dynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.GroupMetasTable\n\terr = dynamodb.TestErrDynamoDBBackend.CheckRequiredTablesIfExistForTest()\n\tassert.NotNil(t, err)\n\tdynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable = \"foo\"\n\terr = dynamodb.TestDynamoDBBackend.CheckRequiredTablesIfExistForTest()\n\tassert.NotNil(t, err)\n\tdynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.TaskStatesTable = taskTable\n\tdynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.GroupMetasTable = \"foo\"\n\terr = dynamodb.TestDynamoDBBackend.CheckRequiredTablesIfExistForTest()\n\tassert.NotNil(t, err)\n\tdynamodb.TestDynamoDBBackend.GetConfig().DynamoDB.GroupMetasTable = groupTable\n}\n\nfunc validateBatchGetItemInput(input *awsdynamodb.BatchGetItemInput) error {\n\tif input == nil {\n\t\treturn fmt.Errorf(\"input is nil\")\n\t}\n\tif len(input.RequestItems) == 0 {\n\t\treturn fmt.Errorf(\"RequestItems cannot be empty\")\n\t}\n\tfor tableName, keysAndAttributes := range input.RequestItems {\n\t\tif tableName == \"\" {\n\t\t\treturn fmt.Errorf(\"table name cannot be empty\")\n\t\t}\n\t\tif len(keysAndAttributes.Keys) == 0 {\n\t\t\treturn fmt.Errorf(\"Keys for table %s cannot be empty\", tableName)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "v2/backends/eager/eager.go",
    "content": "package eager\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// ErrGroupNotFound ...\ntype ErrGroupNotFound struct {\n\tgroupUUID string\n}\n\n// NewErrGroupNotFound returns new instance of ErrGroupNotFound\nfunc NewErrGroupNotFound(groupUUID string) ErrGroupNotFound {\n\treturn ErrGroupNotFound{groupUUID: groupUUID}\n}\n\n// Error implements error interface\nfunc (e ErrGroupNotFound) Error() string {\n\treturn fmt.Sprintf(\"Group not found: %v\", e.groupUUID)\n}\n\n// ErrTasknotFound ...\ntype ErrTasknotFound struct {\n\ttaskUUID string\n}\n\n// NewErrTasknotFound returns new instance of ErrTasknotFound\nfunc NewErrTasknotFound(taskUUID string) ErrTasknotFound {\n\treturn ErrTasknotFound{taskUUID: taskUUID}\n}\n\n// Error implements error interface\nfunc (e ErrTasknotFound) Error() string {\n\treturn fmt.Sprintf(\"Task not found: %v\", e.taskUUID)\n}\n\n// Backend represents an \"eager\" in-memory result backend\ntype Backend struct {\n\tcommon.Backend\n\tgroups     map[string][]string\n\ttasks      map[string][]byte\n\tstateMutex sync.Mutex\n}\n\n// New creates EagerBackend instance\nfunc New() iface.Backend {\n\treturn &Backend{\n\t\tBackend: common.NewBackend(new(config.Config)),\n\t\tgroups:  make(map[string][]string),\n\t\ttasks:   make(map[string][]byte),\n\t}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\ttasks := make([]string, 0, len(taskUUIDs))\n\t// copy every task\n\ttasks = append(tasks, taskUUIDs...)\n\n\tb.groups[groupUUID] = tasks\n\treturn nil\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\ttasks, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn false, NewErrGroupNotFound(groupUUID)\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, v := range tasks {\n\t\tt, err := b.GetState(v)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif t.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\ttaskUUIDs, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn nil, NewErrGroupNotFound(groupUUID)\n\t}\n\n\tret := make([]*tasks.TaskState, 0, groupTaskCount)\n\tfor _, taskUUID := range taskUUIDs {\n\t\tt, err := b.GetState(taskUUID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tret = append(ret, t)\n\t}\n\n\treturn ret, nil\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\treturn true, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\tstate := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\tstate := tasks.NewReceivedTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\tstate := tasks.NewStartedTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tstate := tasks.NewRetryTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\tstate := tasks.NewSuccessTaskState(signature, results)\n\treturn b.updateState(state)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\tstate := tasks.NewFailureTaskState(signature, err)\n\treturn b.updateState(state)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\ttasktStateBytes, ok := b.tasks[taskUUID]\n\tif !ok {\n\t\treturn nil, NewErrTasknotFound(taskUUID)\n\t}\n\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader(tasktStateBytes))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to unmarshal task state %v\", b)\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\t_, ok := b.tasks[taskUUID]\n\tif !ok {\n\t\treturn NewErrTasknotFound(taskUUID)\n\t}\n\n\tdelete(b.tasks, taskUUID)\n\treturn nil\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\t_, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn NewErrGroupNotFound(groupUUID)\n\t}\n\n\tdelete(b.groups, groupUUID)\n\treturn nil\n}\n\nfunc (b *Backend) updateState(s *tasks.TaskState) error {\n\t// simulate the behavior of json marshal/unmarshal\n\tb.stateMutex.Lock()\n\tdefer b.stateMutex.Unlock()\n\tmsg, err := json.Marshal(s)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Marshal task state error: %v\", err)\n\t}\n\n\tb.tasks[s.TaskUUID] = msg\n\treturn nil\n}\n"
  },
  {
    "path": "v2/backends/eager/eager_test.go",
    "content": "package eager_test\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/eager\"\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype EagerBackendTestSuite struct {\n\tsuite.Suite\n\n\tbackend iface.Backend\n\tst      []*tasks.Signature\n\tgroups  []struct {\n\t\tid    string\n\t\ttasks []string\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) SetupSuite() {\n\t// prepare common test data\n\ts.backend = eager.New()\n\n\t// 2 non-group state\n\ts.st = []*tasks.Signature{\n\t\t{UUID: \"1\"},\n\t\t{UUID: \"2\"},\n\t\t{UUID: \"3\"},\n\t\t{UUID: \"4\"},\n\t\t{UUID: \"5\"},\n\t\t{UUID: \"6\"},\n\t}\n\n\tfor _, t := range s.st {\n\t\ts.backend.SetStatePending(t)\n\t}\n\n\t// groups\n\ts.groups = []struct {\n\t\tid    string\n\t\ttasks []string\n\t}{\n\t\t{\"group1\", []string{\"1-3\", \"1-4\"}},\n\t\t{\"group2\", []string{\"2-1\", \"2-2\", \"2-3\"}},\n\t\t{\"group3\", []string(nil)},\n\t\t{\"group4\", []string{\"4-1\", \"4-2\", \"4-3\", \"4-4\"}},\n\t\t{\"group5\", []string{\"5-1\", \"5-2\"}},\n\t}\n\n\tfor _, g := range s.groups {\n\t\tfor _, t := range g.tasks {\n\t\t\tsig := &tasks.Signature{\n\t\t\t\tUUID:           t,\n\t\t\t\tGroupUUID:      g.id,\n\t\t\t\tGroupTaskCount: len(g.tasks),\n\t\t\t}\n\t\t\ts.st = append(s.st, sig)\n\n\t\t\t// default state is pending\n\t\t\ts.backend.SetStatePending(sig)\n\t\t}\n\n\t\ts.Nil(s.backend.InitGroup(g.id, g.tasks))\n\t}\n\n\t// prepare for TestInitGroup\n\ts.Nil(s.backend.PurgeGroupMeta(s.groups[4].id))\n}\n\n//\n// Test Cases\n//\n\nfunc (s *EagerBackendTestSuite) TestInitGroup() {\n\t// group 5\n\t{\n\t\tg := s.groups[4]\n\t\ts.Nil(s.backend.InitGroup(g.id, g.tasks))\n\t}\n\n\t// group3 -- nil as task list\n\t{\n\t\tg := s.groups[2]\n\t\ts.Nil(s.backend.InitGroup(g.id, g.tasks))\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestGroupCompleted() {\n\t// group 1\n\t{\n\t\t// all tasks are pending\n\t\tg := s.groups[0]\n\t\tcompleted, err := s.backend.GroupCompleted(g.id, len(g.tasks))\n\t\ts.False(completed)\n\t\ts.Nil(err)\n\n\t\t// make these tasks success\n\t\tfor _, id := range g.tasks {\n\t\t\tt := s.getTaskSignature(id)\n\t\t\ts.NotNil(t)\n\t\t\tif t == nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ts.backend.SetStateSuccess(t, nil)\n\t\t}\n\n\t\tcompleted, err = s.backend.GroupCompleted(g.id, len(g.tasks))\n\t\ts.True(completed)\n\t\ts.Nil(err)\n\t}\n\n\t// group 2\n\t{\n\t\tg := s.groups[1]\n\n\t\tcompleted, err := s.backend.GroupCompleted(g.id, len(g.tasks))\n\t\ts.False(completed)\n\t\ts.Nil(err)\n\n\t\t// make these tasks failure\n\t\tfor _, id := range g.tasks {\n\t\t\tt := s.getTaskSignature(id)\n\t\t\ts.NotNil(t)\n\t\t\tif t == nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ts.backend.SetStateFailure(t, \"just a test\")\n\t\t}\n\n\t\tcompleted, err = s.backend.GroupCompleted(g.id, len(g.tasks))\n\t\ts.True(completed)\n\t\ts.Nil(err)\n\t}\n\n\t{\n\t\t// call on a not-existed group\n\t\tcompleted, err := s.backend.GroupCompleted(\"\", 0)\n\t\ts.False(completed)\n\t\ts.NotNil(err)\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestGroupTaskStates() {\n\t// group 4\n\t{\n\t\tg := s.groups[3]\n\n\t\t// set failure state with taskUUID as error message\n\t\tfor _, id := range g.tasks {\n\t\t\tt := s.getTaskSignature(id)\n\t\t\ts.NotNil(t)\n\t\t\tif t == nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ts.backend.SetStateFailure(t, t.UUID)\n\t\t}\n\n\t\t// get states back\n\t\tts, err := s.backend.GroupTaskStates(g.id, len(g.tasks))\n\t\ts.NotNil(ts)\n\t\ts.Nil(err)\n\t\tfor _, t := range ts {\n\t\t\ts.Equal(t.TaskUUID, t.Error)\n\t\t}\n\t}\n\n\t{\n\t\t// call on a not-existed group\n\t\tts, err := s.backend.GroupTaskStates(\"\", 0)\n\t\ts.Nil(ts)\n\t\ts.NotNil(err)\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStatePending() {\n\t// task 1\n\t{\n\t\tt := s.st[0]\n\n\t\t// change this state to receiving\n\t\ts.backend.SetStateReceived(t)\n\n\t\t// change it back to pending\n\t\ts.backend.SetStatePending(t)\n\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StatePending, st.State)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateReceived() {\n\t// task2\n\t{\n\t\tt := s.st[1]\n\t\ts.backend.SetStateReceived(t)\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StateReceived, st.State)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateStarted() {\n\t// task3\n\t{\n\t\tt := s.st[2]\n\t\ts.backend.SetStateStarted(t)\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StateStarted, st.State)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateSuccess() {\n\t// task4\n\t{\n\t\tt := s.st[3]\n\t\ttaskResults := []*tasks.TaskResult{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: json.Number(\"300.0\"),\n\t\t\t},\n\t\t}\n\t\ts.backend.SetStateSuccess(t, taskResults)\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\ts.NotNil(st)\n\n\t\ts.Equal(tasks.StateSuccess, st.State)\n\t\ts.Equal(taskResults, st.Results)\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateFailure() {\n\t// task5\n\t{\n\t\tt := s.st[4]\n\t\ts.backend.SetStateFailure(t, \"error\")\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StateFailure, st.State)\n\t\t\ts.Equal(\"error\", st.Error)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestSetStateRetry() {\n\t// task6\n\t{\n\t\tt := s.st[5]\n\t\ts.backend.SetStateRetry(t)\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.Nil(err)\n\t\tif st != nil {\n\t\t\ts.Equal(tasks.StateRetry, st.State)\n\t\t}\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestGetState() {\n\t// get something not existed -- empty string\n\tst, err := s.backend.GetState(\"\")\n\ts.Nil(st)\n\ts.NotNil(err)\n}\n\nfunc (s *EagerBackendTestSuite) TestPurgeState() {\n\t// task6\n\t{\n\t\tt := s.st[5]\n\t\tst, err := s.backend.GetState(t.UUID)\n\t\ts.NotNil(st)\n\t\ts.Nil(err)\n\n\t\t// purge it\n\t\ts.Nil(s.backend.PurgeState(t.UUID))\n\n\t\t// should be not found\n\t\tst, err = s.backend.GetState(t.UUID)\n\t\ts.Nil(st)\n\t\ts.NotNil(err)\n\t}\n\n\t{\n\t\t// purge a not-existed state\n\t\ts.NotNil(s.backend.PurgeState(\"\"))\n\t}\n}\n\nfunc (s *EagerBackendTestSuite) TestPurgeGroupMeta() {\n\t// group4\n\t{\n\t\tg := s.groups[3]\n\t\tts, err := s.backend.GroupTaskStates(g.id, len(g.tasks))\n\t\ts.NotNil(ts)\n\t\ts.Nil(err)\n\n\t\t// purge group\n\t\ts.Nil(s.backend.PurgeGroupMeta(g.id))\n\n\t\t// should be not found\n\t\tts, err = s.backend.GroupTaskStates(g.id, len(g.tasks))\n\t\ts.Nil(ts)\n\t\ts.NotNil(err)\n\t}\n\n\t{\n\t\t// purge a not-existed group\n\t\ts.NotNil(s.backend.PurgeGroupMeta(\"\"))\n\t}\n}\n\n//\n// internal method\n//\nfunc (s *EagerBackendTestSuite) getTaskSignature(taskUUID string) *tasks.Signature {\n\tfor _, v := range s.st {\n\t\tif v.UUID == taskUUID {\n\t\t\treturn v\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc TestEagerBackendMain(t *testing.T) {\n\tsuite.Run(t, &EagerBackendTestSuite{})\n}\n"
  },
  {
    "path": "v2/backends/iface/dynamodb/api.go",
    "content": "package dynamodb\n\nimport (\n\t\"context\"\n\n\t\"github.com/aws/aws-sdk-go-v2/service/dynamodb\"\n)\n\n// API is an interface for DynamoDB API.\ntype API interface {\n\tPutItem(ctx context.Context, params *dynamodb.PutItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error)\n\tGetItem(ctx context.Context, params *dynamodb.GetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error)\n\tUpdateItem(ctx context.Context, params *dynamodb.UpdateItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.UpdateItemOutput, error)\n\tDeleteItem(ctx context.Context, params *dynamodb.DeleteItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DeleteItemOutput, error)\n\tBatchGetItem(ctx context.Context, params *dynamodb.BatchGetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.BatchGetItemOutput, error)\n\tListTables(ctx context.Context, params *dynamodb.ListTablesInput, optFns ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error)\n}\n"
  },
  {
    "path": "v2/backends/iface/interfaces.go",
    "content": "package iface\n\nimport (\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// Backend - a common interface for all result backends\ntype Backend interface {\n\t// Group related functions\n\tInitGroup(groupUUID string, taskUUIDs []string) error\n\tGroupCompleted(groupUUID string, groupTaskCount int) (bool, error)\n\tGroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error)\n\tTriggerChord(groupUUID string) (bool, error)\n\n\t// Setting / getting task state\n\tSetStatePending(signature *tasks.Signature) error\n\tSetStateReceived(signature *tasks.Signature) error\n\tSetStateStarted(signature *tasks.Signature) error\n\tSetStateRetry(signature *tasks.Signature) error\n\tSetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error\n\tSetStateFailure(signature *tasks.Signature, err string) error\n\tGetState(taskUUID string) (*tasks.TaskState, error)\n\n\t// Purging stored stored tasks states and group meta data\n\tIsAMQP() bool\n\tPurgeState(taskUUID string) error\n\tPurgeGroupMeta(groupUUID string) error\n}\n"
  },
  {
    "path": "v2/backends/memcache/memcache.go",
    "content": "package memcache\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\n\tgomemcache \"github.com/bradfitz/gomemcache/memcache\"\n)\n\n// Backend represents a Memcache result backend\ntype Backend struct {\n\tcommon.Backend\n\tservers []string\n\tclient  *gomemcache.Client\n}\n\n// New creates Backend instance\nfunc New(cnf *config.Config, servers []string) iface.Backend {\n\treturn &Backend{\n\t\tBackend: common.NewBackend(cnf),\n\t\tservers: servers,\n\t}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tgroupMeta := &tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n\n\tencoded, err := json.Marshal(&groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.getClient().Set(&gomemcache.Item{\n\t\tKey:        groupUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t})\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(groupMeta.TaskUUIDs...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn []*tasks.TaskState{}, err\n\t}\n\n\treturn b.getStates(groupMeta.TaskUUIDs...)\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Chord has already been triggered, return false (should not trigger again)\n\tif groupMeta.ChordTriggered {\n\t\treturn false, nil\n\t}\n\n\t// If group meta is locked, wait until it's unlocked\n\tfor groupMeta.Lock {\n\t\tgroupMeta, _ = b.getGroupMeta(groupUUID)\n\t\tlog.WARNING.Print(\"Group meta locked, waiting\")\n\t\ttime.Sleep(time.Millisecond * 5)\n\t}\n\n\t// Acquire lock\n\tif err = b.lockGroupMeta(groupMeta); err != nil {\n\t\treturn false, err\n\t}\n\tdefer b.unlockGroupMeta(groupMeta)\n\n\t// Update the group meta data\n\tgroupMeta.ChordTriggered = true\n\tencoded, err := json.Marshal(&groupMeta)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif err = b.getClient().Replace(&gomemcache.Item{\n\t\tKey:        groupUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t}); err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\ttaskState := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\ttaskState := tasks.NewStartedTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tstate := tasks.NewRetryTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\treturn b.updateState(taskState)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\treturn b.updateState(taskState)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\titem, err := b.getClient().Get(taskUUID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader(item.Value))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\treturn b.getClient().Delete(taskUUID)\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\treturn b.getClient().Delete(groupUUID)\n}\n\n// updateState saves current task state\nfunc (b *Backend) updateState(taskState *tasks.TaskState) error {\n\tencoded, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.getClient().Set(&gomemcache.Item{\n\t\tKey:        taskState.TaskUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t})\n}\n\n// lockGroupMeta acquires lock on group meta data\nfunc (b *Backend) lockGroupMeta(groupMeta *tasks.GroupMeta) error {\n\tgroupMeta.Lock = true\n\tencoded, err := json.Marshal(groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.getClient().Set(&gomemcache.Item{\n\t\tKey:        groupMeta.GroupUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t})\n}\n\n// unlockGroupMeta releases lock on group meta data\nfunc (b *Backend) unlockGroupMeta(groupMeta *tasks.GroupMeta) error {\n\tgroupMeta.Lock = false\n\tencoded, err := json.Marshal(groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.getClient().Set(&gomemcache.Item{\n\t\tKey:        groupMeta.GroupUUID,\n\t\tValue:      encoded,\n\t\tExpiration: b.getExpirationTimestamp(),\n\t})\n}\n\n// getGroupMeta retrieves group meta data, convenience function to avoid repetition\nfunc (b *Backend) getGroupMeta(groupUUID string) (*tasks.GroupMeta, error) {\n\titem, err := b.getClient().Get(groupUUID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroupMeta := new(tasks.GroupMeta)\n\tdecoder := json.NewDecoder(bytes.NewReader(item.Value))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(groupMeta); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn groupMeta, nil\n}\n\n// getStates returns multiple task states\nfunc (b *Backend) getStates(taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\tstates := make([]*tasks.TaskState, len(taskUUIDs))\n\n\tfor i, taskUUID := range taskUUIDs {\n\t\titem, err := b.getClient().Get(taskUUID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tstate := new(tasks.TaskState)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(item.Value))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(state); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tstates[i] = state\n\t}\n\n\treturn states, nil\n}\n\n// getExpirationTimestamp returns expiration timestamp\nfunc (b *Backend) getExpirationTimestamp() int32 {\n\texpiresIn := b.GetConfig().ResultsExpireIn\n\tif expiresIn == 0 {\n\t\t// // expire results after 1 hour by default\n\t\texpiresIn = config.DefaultResultsExpireIn\n\t}\n\treturn int32(time.Now().Unix() + int64(expiresIn))\n}\n\n// getClient returns or creates instance of Memcache client\nfunc (b *Backend) getClient() *gomemcache.Client {\n\tif b.client == nil {\n\t\tb.client = gomemcache.New(b.servers...)\n\t}\n\treturn b.client\n}\n"
  },
  {
    "path": "v2/backends/memcache/memcache_test.go",
    "content": "package memcache_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/memcache\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGroupCompleted(t *testing.T) {\n\tmemcacheURL := os.Getenv(\"MEMCACHE_URL\")\n\tif memcacheURL == \"\" {\n\t\tt.Skip(\"MEMCACHE_URL is not defined\")\n\t}\n\n\tgroupUUID := \"testGroupUUID\"\n\ttask1 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID1\",\n\t\tGroupUUID: groupUUID,\n\t}\n\ttask2 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID2\",\n\t\tGroupUUID: groupUUID,\n\t}\n\n\tbackend := memcache.New(new(config.Config), []string{memcacheURL})\n\n\t// Cleanup before the test\n\tbackend.PurgeState(task1.UUID)\n\tbackend.PurgeState(task2.UUID)\n\tbackend.PurgeGroupMeta(groupUUID)\n\n\tgroupCompleted, err := backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"memcache: cache miss\", err.Error())\n\t}\n\n\tbackend.InitGroup(groupUUID, []string{task1.UUID, task2.UUID})\n\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"memcache: cache miss\", err.Error())\n\t}\n\n\tbackend.SetStatePending(task1)\n\tbackend.SetStateStarted(task2)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\ttaskResults := []*tasks.TaskResult{new(tasks.TaskResult)}\n\tbackend.SetStateStarted(task1)\n\tbackend.SetStateSuccess(task2, taskResults)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.SetStateFailure(task1, \"Some error\")\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, groupCompleted)\n\t}\n}\n\nfunc TestGetState(t *testing.T) {\n\tmemcacheURL := os.Getenv(\"MEMCACHE_URL\")\n\tif memcacheURL == \"\" {\n\t\tt.Skip(\"MEMCACHE_URL is not defined\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := memcache.New(new(config.Config), []string{memcacheURL})\n\n\tgo func() {\n\t\tbackend.SetStatePending(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\tbackend.SetStateReceived(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\tbackend.SetStateStarted(signature)\n\t\ttime.Sleep(2 * time.Millisecond)\n\t\ttaskResults := []*tasks.TaskResult{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: 2,\n\t\t\t},\n\t\t}\n\t\tbackend.SetStateSuccess(signature, taskResults)\n\t}()\n\n\tvar (\n\t\ttaskState *tasks.TaskState\n\t\terr       error\n\t)\n\tfor {\n\t\ttaskState, err = backend.GetState(signature.UUID)\n\t\tif taskState == nil {\n\t\t\tassert.Equal(t, \"memcache: cache miss\", err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.NoError(t, err)\n\t\tif taskState.IsCompleted() {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc TestPurgeState(t *testing.T) {\n\tmemcacheURL := os.Getenv(\"MEMCACHE_URL\")\n\tif memcacheURL == \"\" {\n\t\tt.Skip(\"MEMCACHE_URL is not defined\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := memcache.New(new(config.Config), []string{memcacheURL})\n\n\tbackend.SetStatePending(signature)\n\ttaskState, err := backend.GetState(signature.UUID)\n\tassert.NotNil(t, taskState)\n\tassert.NoError(t, err)\n\n\tbackend.PurgeState(taskState.TaskUUID)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Nil(t, taskState)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "v2/backends/mongo/mongodb.go",
    "content": "package mongo\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.mongodb.org/mongo-driver/bson\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\t\"go.mongodb.org/mongo-driver/mongo/options\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// Backend represents a MongoDB result backend\ntype Backend struct {\n\tcommon.Backend\n\tclient *mongo.Client\n\ttc     *mongo.Collection\n\tgmc    *mongo.Collection\n\tonce   sync.Once\n}\n\n// New creates Backend instance\nfunc New(cnf *config.Config) (iface.Backend, error) {\n\tbackend := &Backend{\n\t\tBackend: common.NewBackend(cnf),\n\t\tonce:    sync.Once{},\n\t}\n\n\treturn backend, nil\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tgroupMeta := &tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n\t_, err := b.groupMetasCollection().InsertOne(context.Background(), groupMeta)\n\treturn err\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(groupMeta.TaskUUIDs...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn []*tasks.TaskState{}, err\n\t}\n\n\treturn b.getStates(groupMeta.TaskUUIDs...)\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never triggered multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\tquery := bson.M{\n\t\t\"_id\":             groupUUID,\n\t\t\"chord_triggered\": false,\n\t}\n\tchange := bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"chord_triggered\": true,\n\t\t},\n\t}\n\n\t_, err := b.groupMetasCollection().UpdateOne(context.Background(), query, change, options.Update())\n\n\tif err != nil {\n\t\tif err == mongo.ErrNoDocuments {\n\t\t\tlog.WARNING.Printf(\"Chord already triggered for group %s\", groupUUID)\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\tupdate := bson.M{\n\t\t\"state\":      tasks.StatePending,\n\t\t\"task_name\":  signature.Name,\n\t\t\"created_at\": time.Now().UTC(),\n\t}\n\treturn b.updateState(signature, update)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\tupdate := bson.M{\"state\": tasks.StateReceived}\n\treturn b.updateState(signature, update)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\tupdate := bson.M{\"state\": tasks.StateStarted}\n\treturn b.updateState(signature, update)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tupdate := bson.M{\"state\": tasks.StateRetry}\n\treturn b.updateState(signature, update)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\tdecodedResults := b.decodeResults(results)\n\tupdate := bson.M{\n\t\t\"state\":   tasks.StateSuccess,\n\t\t\"results\": decodedResults,\n\t\t\"delete_at\":     time.Now().Add(time.Duration(b.GetConfig().ResultsExpireIn) * time.Second),\n\t}\n\treturn b.updateState(signature, update)\n}\n\n// decodeResults detects & decodes json strings in TaskResult.Value and returns a new slice\nfunc (b *Backend) decodeResults(results []*tasks.TaskResult) []*tasks.TaskResult {\n\tl := len(results)\n\tjsonResults := make([]*tasks.TaskResult, l)\n\tfor i, result := range results {\n\t\tjsonResult := new(bson.M)\n\t\tresultType := reflect.TypeOf(result.Value).Kind()\n\t\tif resultType == reflect.String {\n\t\t\terr := json.NewDecoder(strings.NewReader(result.Value.(string))).Decode(&jsonResult)\n\t\t\tif err == nil {\n\t\t\t\tjsonResults[i] = &tasks.TaskResult{\n\t\t\t\t\tType:  \"json\",\n\t\t\t\t\tValue: jsonResult,\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tjsonResults[i] = result\n\t}\n\treturn jsonResults\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\tupdate := bson.M{\n\t\t\"state\": tasks.StateFailure,\n\t\t\"error\": err,\n\t\t\"delete_at\":   time.Now().Add(time.Duration(b.GetConfig().ResultsExpireIn) * time.Second),\n\t}\n\treturn b.updateState(signature, update)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\tstate := &tasks.TaskState{}\n\terr := b.tasksCollection().FindOne(context.Background(), bson.M{\"_id\": taskUUID}).Decode(state)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\t_, err := b.tasksCollection().DeleteOne(context.Background(), bson.M{\"_id\": taskUUID})\n\treturn err\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\t_, err := b.groupMetasCollection().DeleteOne(context.Background(), bson.M{\"_id\": groupUUID})\n\treturn err\n}\n\n// lockGroupMeta acquires lock on groupUUID document\nfunc (b *Backend) lockGroupMeta(groupUUID string) error {\n\tquery := bson.M{\n\t\t\"_id\":  groupUUID,\n\t\t\"lock\": false,\n\t}\n\tchange := bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"lock\": true,\n\t\t},\n\t}\n\n\t_, err := b.groupMetasCollection().UpdateOne(context.Background(), query, change, options.Update().SetUpsert(true))\n\n\treturn err\n}\n\n// unlockGroupMeta releases lock on groupUUID document\nfunc (b *Backend) unlockGroupMeta(groupUUID string) error {\n\tupdate := bson.M{\"$set\": bson.M{\"lock\": false}}\n\t_, err := b.groupMetasCollection().UpdateOne(context.Background(), bson.M{\"_id\": groupUUID}, update, options.Update())\n\treturn err\n}\n\n// getGroupMeta retrieves group meta data, convenience function to avoid repetition\nfunc (b *Backend) getGroupMeta(groupUUID string) (*tasks.GroupMeta, error) {\n\tgroupMeta := &tasks.GroupMeta{}\n\tquery := bson.M{\"_id\": groupUUID}\n\n\terr := b.groupMetasCollection().FindOne(context.Background(), query).Decode(groupMeta)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn groupMeta, nil\n}\n\n// getStates returns multiple task states\nfunc (b *Backend) getStates(taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\tstates := make([]*tasks.TaskState, 0, len(taskUUIDs))\n\tcur, err := b.tasksCollection().Find(context.Background(), bson.M{\"_id\": bson.M{\"$in\": taskUUIDs}})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer cur.Close(context.Background())\n\n\tfor cur.Next(context.Background()) {\n\t\tstate := &tasks.TaskState{}\n\t\tif err := cur.Decode(state); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tstates = append(states, state)\n\t}\n\tif cur.Err() != nil {\n\t\treturn nil, err\n\t}\n\treturn states, nil\n}\n\n// updateState saves current task state\nfunc (b *Backend) updateState(signature *tasks.Signature, update bson.M) error {\n\tupdate = bson.M{\"$set\": update}\n\t_, err := b.tasksCollection().UpdateOne(context.Background(), bson.M{\"_id\": signature.UUID}, update, options.Update().SetUpsert(true))\n\treturn err\n}\n\nfunc (b *Backend) tasksCollection() *mongo.Collection {\n\tb.once.Do(func() {\n\t\tb.connect()\n\t})\n\n\treturn b.tc\n}\n\nfunc (b *Backend) groupMetasCollection() *mongo.Collection {\n\tb.once.Do(func() {\n\t\tb.connect()\n\t})\n\n\treturn b.gmc\n}\n\n// connect creates the underlying mgo connection if it doesn't exist\n// creates required indexes for our collections\nfunc (b *Backend) connect() error {\n\tclient, err := b.dial()\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.client = client\n\n\tdatabase := \"machinery\"\n\n\tif b.GetConfig().MongoDB != nil {\n\t\tdatabase = b.GetConfig().MongoDB.Database\n\t}\n\n\tb.tc = b.client.Database(database).Collection(\"tasks\")\n\tb.gmc = b.client.Database(database).Collection(\"group_metas\")\n\n\terr = b.createMongoIndexes(database)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// dial connects to mongo with TLSConfig if provided\n// else connects via ResultBackend uri\nfunc (b *Backend) dial() (*mongo.Client, error) {\n\n\tif b.GetConfig().MongoDB != nil && b.GetConfig().MongoDB.Client != nil {\n\t\treturn b.GetConfig().MongoDB.Client, nil\n\t}\n\n\turi := b.GetConfig().ResultBackend\n\tif strings.HasPrefix(uri, \"mongodb://\") == false &&\n\t\tstrings.HasPrefix(uri, \"mongodb+srv://\") == false {\n\t\turi = fmt.Sprintf(\"mongodb://%s\", uri)\n\t}\n\n\tclient, err := mongo.NewClient(options.Client().ApplyURI(uri))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\tif err := client.Connect(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn client, nil\n}\n\n// createMongoIndexes ensures all indexes are in place\nfunc (b *Backend) createMongoIndexes(database string) error {\n\n\ttasksCollection := b.client.Database(database).Collection(\"tasks\")\n\n\t_, err := tasksCollection.Indexes().CreateMany(\n\t\tcontext.Background(), []mongo.IndexModel{\n\t\t\t{\n\t\t\t\tKeys:    bson.M{\"delete_at\": 1},\n\t\t\t\tOptions: options.Index().SetBackground(true).SetExpireAfterSeconds(0),\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "v2/backends/mongo/mongodb_test.go",
    "content": "package mongo_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/backends/mongo\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar (\n\tgroupUUID = \"123456\"\n\ttaskUUIDs = []string{\"1\", \"2\", \"3\"}\n)\n\nfunc newBackend() (iface.Backend, error) {\n\tcnf := &config.Config{\n\t\tResultBackend:   os.Getenv(\"MONGODB_URL\"),\n\t\tResultsExpireIn: 30,\n\t}\n\tbackend, err := mongo.New(cnf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbackend.PurgeGroupMeta(groupUUID)\n\tfor _, taskUUID := range taskUUIDs {\n\t\tbackend.PurgeState(taskUUID)\n\t}\n\n\tif err := backend.InitGroup(groupUUID, taskUUIDs); err != nil {\n\t\treturn nil, err\n\t}\n\treturn backend, nil\n}\n\nfunc TestNew(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif assert.NoError(t, err) {\n\t\tassert.NotNil(t, backend)\n\t}\n}\n\nfunc TestSetStatePending(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = backend.SetStatePending(&tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t})\n\tif assert.NoError(t, err) {\n\t\ttaskState, err := backend.GetState(taskUUIDs[0])\n\t\tif assert.NoError(t, err) {\n\t\t\tassert.Equal(t, tasks.StatePending, taskState.State, \"Not StatePending\")\n\t\t}\n\t}\n}\n\nfunc TestSetStateReceived(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = backend.SetStateReceived(&tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t})\n\tif assert.NoError(t, err) {\n\t\ttaskState, err := backend.GetState(taskUUIDs[0])\n\t\tif assert.NoError(t, err) {\n\t\t\tassert.Equal(t, tasks.StateReceived, taskState.State, \"Not StateReceived\")\n\t\t}\n\t}\n}\n\nfunc TestSetStateStarted(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = backend.SetStateStarted(&tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t})\n\tif assert.NoError(t, err) {\n\t\ttaskState, err := backend.GetState(taskUUIDs[0])\n\t\tif assert.NoError(t, err) {\n\t\t\tassert.Equal(t, tasks.StateStarted, taskState.State, \"Not StateStarted\")\n\t\t}\n\t}\n}\n\nfunc TestSetStateSuccess(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tresultType := \"float64\"\n\tresultValue := float64(88.5)\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t}\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  resultType,\n\t\t\tValue: resultValue,\n\t\t},\n\t}\n\terr = backend.SetStateSuccess(signature, taskResults)\n\tassert.NoError(t, err)\n\n\ttaskState, err := backend.GetState(taskUUIDs[0])\n\tassert.NoError(t, err)\n\tassert.Equal(t, tasks.StateSuccess, taskState.State, \"Not StateSuccess\")\n\tassert.Equal(t, resultType, taskState.Results[0].Type, \"Wrong result type\")\n\tassert.Equal(t, float64(resultValue), taskState.Results[0].Value.(float64), \"Wrong result value\")\n}\n\nfunc TestSetStateFailure(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tfailString := \"Fail is ok\"\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t}\n\terr = backend.SetStateFailure(signature, failString)\n\tassert.NoError(t, err)\n\n\ttaskState, err := backend.GetState(taskUUIDs[0])\n\tassert.NoError(t, err)\n\tassert.Equal(t, tasks.StateFailure, taskState.State, \"Not StateSuccess\")\n\tassert.Equal(t, failString, taskState.Error, \"Wrong fail error\")\n}\n\nfunc TestGroupCompleted(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttaskResultsState := make(map[string]string)\n\n\tisCompleted, err := backend.GroupCompleted(groupUUID, len(taskUUIDs))\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, isCompleted, \"Actually group is not completed\")\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID: taskUUIDs[0],\n\t}\n\terr = backend.SetStateFailure(signature, \"Fail is ok\")\n\tassert.NoError(t, err)\n\ttaskResultsState[taskUUIDs[0]] = tasks.StateFailure\n\n\tsignature = &tasks.Signature{\n\t\tUUID: taskUUIDs[1],\n\t}\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  \"string\",\n\t\t\tValue: \"Result ok\",\n\t\t},\n\t}\n\terr = backend.SetStateSuccess(signature, taskResults)\n\tassert.NoError(t, err)\n\ttaskResultsState[taskUUIDs[1]] = tasks.StateSuccess\n\n\tsignature = &tasks.Signature{\n\t\tUUID: taskUUIDs[2],\n\t}\n\terr = backend.SetStateSuccess(signature, taskResults)\n\tassert.NoError(t, err)\n\ttaskResultsState[taskUUIDs[2]] = tasks.StateSuccess\n\n\tisCompleted, err = backend.GroupCompleted(groupUUID, len(taskUUIDs))\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, isCompleted, \"Actually group is completed\")\n\t}\n\n\ttaskStates, err := backend.GroupTaskStates(groupUUID, len(taskUUIDs))\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, len(taskStates), len(taskUUIDs), \"Wrong len tasksStates\")\n\tfor _, taskState := range taskStates {\n\t\tassert.Equal(\n\t\t\tt,\n\t\t\ttaskResultsState[taskState.TaskUUID],\n\t\t\ttaskState.State,\n\t\t\t\"Wrong state on\", taskState.TaskUUID,\n\t\t)\n\t}\n}\n\nfunc TestGroupStates(t *testing.T) {\n\tif os.Getenv(\"MONGODB_URL\") == \"\" {\n\t\tt.Skip(\"MONGODB_URL is not defined\")\n\t}\n\n\tbackend, err := newBackend()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttaskStates, err := backend.GroupTaskStates(groupUUID, len(taskUUIDs))\n\tassert.NoError(t, err)\n\tfor i, taskState := range taskStates {\n\t\tassert.Equal(t, taskUUIDs[i], taskState.TaskUUID)\n\t}\n}\n"
  },
  {
    "path": "v2/backends/null/null.go",
    "content": "package null\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// ErrGroupNotFound ...\ntype ErrGroupNotFound struct {\n\tgroupUUID string\n}\n\n// NewErrGroupNotFound returns new instance of ErrGroupNotFound\nfunc NewErrGroupNotFound(groupUUID string) ErrGroupNotFound {\n\treturn ErrGroupNotFound{groupUUID: groupUUID}\n}\n\n// Error implements error interface\nfunc (e ErrGroupNotFound) Error() string {\n\treturn fmt.Sprintf(\"Group not found: %v\", e.groupUUID)\n}\n\n// ErrTasknotFound ...\ntype ErrTasknotFound struct {\n\ttaskUUID string\n}\n\n// NewErrTasknotFound returns new instance of ErrTasknotFound\nfunc NewErrTasknotFound(taskUUID string) ErrTasknotFound {\n\treturn ErrTasknotFound{taskUUID: taskUUID}\n}\n\n// Error implements error interface\nfunc (e ErrTasknotFound) Error() string {\n\treturn fmt.Sprintf(\"Task not found: %v\", e.taskUUID)\n}\n\n// Backend represents an \"null\" result backend\ntype Backend struct {\n\tcommon.Backend\n\tgroups map[string]struct{}\n}\n\n// New creates NullBackend instance\nfunc New() iface.Backend {\n\treturn &Backend{\n\t\tBackend: common.NewBackend(new(config.Config)),\n\t\tgroups:  make(map[string]struct{}),\n\t}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tb.groups[groupUUID] = struct{}{}\n\treturn nil\n}\n\n// GroupCompleted returns true (always)\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\t_, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn false, NewErrGroupNotFound(groupUUID)\n\t}\n\n\treturn true, nil\n}\n\n// GroupTaskStates returns null states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\t_, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn nil, NewErrGroupNotFound(groupUUID)\n\t}\n\n\tret := make([]*tasks.TaskState, 0, groupTaskCount)\n\treturn ret, nil\n}\n\n// TriggerChord returns true (always)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\treturn true, nil\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\tstate := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\tstate := tasks.NewReceivedTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\tstate := tasks.NewStartedTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tstate := tasks.NewRetryTaskState(signature)\n\treturn b.updateState(state)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\tstate := tasks.NewSuccessTaskState(signature, results)\n\treturn b.updateState(state)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\tstate := tasks.NewFailureTaskState(signature, err)\n\treturn b.updateState(state)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\treturn nil, NewErrTasknotFound(taskUUID)\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\treturn NewErrTasknotFound(taskUUID)\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\t_, ok := b.groups[groupUUID]\n\tif !ok {\n\t\treturn NewErrGroupNotFound(groupUUID)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Backend) updateState(s *tasks.TaskState) error {\n\treturn nil\n}\n"
  },
  {
    "path": "v2/backends/package.go",
    "content": "package backends\n"
  },
  {
    "path": "v2/backends/redis/goredis.go",
    "content": "package redis\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-redsync/redsync/v4\"\n\tredsyncgoredis \"github.com/go-redsync/redsync/v4/redis/goredis/v9\"\n\t\"github.com/redis/go-redis/v9\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// BackendGR represents a Redis result backend\ntype BackendGR struct {\n\tcommon.Backend\n\trclient  redis.UniversalClient\n\thost     string\n\tpassword string\n\tdb       int\n\t// If set, path to a socket file overrides hostname\n\tsocketPath string\n\tredsync    *redsync.Redsync\n\tredisOnce  sync.Once\n}\n\n// NewGR creates Backend instance\nfunc NewGR(cnf *config.Config, addrs []string, db int) iface.Backend {\n\tb := &BackendGR{\n\t\tBackend: common.NewBackend(cnf),\n\t}\n\tvar password string\n\tvar username string\n\tparts := strings.Split(addrs[0], \"@\")\n\tif len(parts) >= 2 {\n\t\t// with password\n\t\toptions := strings.SplitN(strings.Join(parts[:len(parts)-1], \"@\"), \":\", 2)\n\t\tif len(options) >= 2 {\n\t\t\tusername = options[0]\n\t\t\tpassword = options[1]\n\t\t} else {\n\t\t\tpassword = options[0]\n\t\t}\n\n\t\taddrs[0] = parts[len(parts)-1] // addr is the last one without @\n\t}\n\n\tropt := &redis.UniversalOptions{\n\t\tAddrs:    addrs,\n\t\tDB:       db,\n\t\tUsername: username,\n\t\tPassword: password,\n\t}\n\tif cnf.Redis != nil {\n\t\tropt.MasterName = cnf.Redis.MasterName\n\t}\n\tif cnf.TLSConfig != nil {\n\t\tropt.TLSConfig = cnf.TLSConfig\n\t}\n\n\tif cnf.Redis != nil && cnf.Redis.SentinelPassword != \"\" {\n\t\tropt.SentinelPassword = cnf.Redis.SentinelPassword\n\t}\n\n\tif cnf.Redis != nil && cnf.Redis.ClusterEnabled {\n\t\tb.rclient = redis.NewClusterClient(ropt.Cluster())\n\t} else {\n\t\tb.rclient = redis.NewUniversalClient(ropt)\n\t}\n\tb.redsync = redsync.New(redsyncgoredis.NewPool(b.rclient))\n\treturn b\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *BackendGR) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tgroupMeta := &tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n\n\tencoded, err := json.Marshal(groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texpiration := b.getExpiration()\n\terr = b.rclient.Set(context.Background(), groupUUID, encoded, expiration).Err()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *BackendGR) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(groupMeta.TaskUUIDs...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *BackendGR) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn []*tasks.TaskState{}, err\n\t}\n\n\treturn b.getStates(groupMeta.TaskUUIDs...)\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *BackendGR) TriggerChord(groupUUID string) (bool, error) {\n\tm := b.redsync.NewMutex(\"TriggerChordMutex\")\n\tif err := m.Lock(); err != nil {\n\t\treturn false, err\n\t}\n\tdefer m.Unlock()\n\n\tgroupMeta, err := b.getGroupMeta(groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Chord has already been triggered, return false (should not trigger again)\n\tif groupMeta.ChordTriggered {\n\t\treturn false, nil\n\t}\n\n\t// Set flag to true\n\tgroupMeta.ChordTriggered = true\n\n\t// Update the group meta\n\tencoded, err := json.Marshal(&groupMeta)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\texpiration := b.getExpiration()\n\terr = b.rclient.Set(context.Background(), groupUUID, encoded, expiration).Err()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (b *BackendGR) mergeNewTaskState(newState *tasks.TaskState) {\n\tstate, err := b.GetState(newState.TaskUUID)\n\tif err == nil {\n\t\tnewState.CreatedAt = state.CreatedAt\n\t\tnewState.TaskName = state.TaskName\n\t}\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *BackendGR) SetStatePending(signature *tasks.Signature) error {\n\ttaskState := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(taskState)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *BackendGR) SetStateReceived(signature *tasks.Signature) error {\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *BackendGR) SetStateStarted(signature *tasks.Signature) error {\n\ttaskState := tasks.NewStartedTaskState(signature)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *BackendGR) SetStateRetry(signature *tasks.Signature) error {\n\ttaskState := tasks.NewRetryTaskState(signature)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *BackendGR) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *BackendGR) SetStateFailure(signature *tasks.Signature, err string) error {\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\tb.mergeNewTaskState(taskState)\n\treturn b.updateState(taskState)\n}\n\n// GetState returns the latest task state\nfunc (b *BackendGR) GetState(taskUUID string) (*tasks.TaskState, error) {\n\n\titem, err := b.rclient.Get(context.Background(), taskUUID).Bytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader(item))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *BackendGR) PurgeState(taskUUID string) error {\n\terr := b.rclient.Del(context.Background(), taskUUID).Err()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *BackendGR) PurgeGroupMeta(groupUUID string) error {\n\terr := b.rclient.Del(context.Background(), groupUUID).Err()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getGroupMeta retrieves group meta data, convenience function to avoid repetition\nfunc (b *BackendGR) getGroupMeta(groupUUID string) (*tasks.GroupMeta, error) {\n\titem, err := b.rclient.Get(context.Background(), groupUUID).Bytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroupMeta := new(tasks.GroupMeta)\n\tdecoder := json.NewDecoder(bytes.NewReader(item))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(groupMeta); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn groupMeta, nil\n}\n\n// getStates returns multiple task states\nfunc (b *BackendGR) getStates(taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\ttaskStates := make([]*tasks.TaskState, len(taskUUIDs))\n\t// to avoid CROSSSLOT error, use pipeline\n\tcmders, err := b.rclient.Pipelined(context.Background(), func(pipeliner redis.Pipeliner) error {\n\t\tfor _, uuid := range taskUUIDs {\n\t\t\tpipeliner.Get(context.Background(), uuid)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn taskStates, err\n\t}\n\tfor i, cmder := range cmders {\n\t\tstateBytes, err1 := cmder.(*redis.StringCmd).Bytes()\n\t\tif err1 != nil {\n\t\t\treturn taskStates, err1\n\t\t}\n\t\ttaskState := new(tasks.TaskState)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(stateBytes))\n\t\tdecoder.UseNumber()\n\t\tif err1 = decoder.Decode(taskState); err1 != nil {\n\t\t\tlog.ERROR.Print(err1)\n\t\t\treturn taskStates, err1\n\t\t}\n\t\ttaskStates[i] = taskState\n\t}\n\n\treturn taskStates, nil\n}\n\n// updateState saves current task state\nfunc (b *BackendGR) updateState(taskState *tasks.TaskState) error {\n\tencoded, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texpiration := b.getExpiration()\n\t_, err = b.rclient.Set(context.Background(), taskState.TaskUUID, encoded, expiration).Result()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getExpiration returns expiration for a stored task state\nfunc (b *BackendGR) getExpiration() time.Duration {\n\texpiresIn := b.GetConfig().ResultsExpireIn\n\tif expiresIn == 0 {\n\t\t// expire results after 1 hour by default\n\t\texpiresIn = config.DefaultResultsExpireIn\n\t}\n\n\treturn time.Duration(expiresIn) * time.Second\n}\n"
  },
  {
    "path": "v2/backends/redis/goredis_test.go",
    "content": "package redis_test\n\nimport (\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/redis\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc getRedisG() iface.Backend {\n\t// host1:port1,host2:port2\n\tredisURL := os.Getenv(\"REDIS_URL_GR\")\n\t//redisPassword := os.Getenv(\"REDIS_PASSWORD\")\n\tif redisURL == \"\" {\n\t\treturn nil\n\t}\n\tbackend := redis.NewGR(new(config.Config), strings.Split(redisURL, \",\"), 0)\n\treturn backend\n}\n\nfunc TestGroupCompletedGR(t *testing.T) {\n\tbackend := getRedisG()\n\tif backend == nil {\n\t\tt.Skip()\n\t}\n\n\tgroupUUID := \"testGroupUUID\"\n\ttask1 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID1\",\n\t\tGroupUUID: groupUUID,\n\t}\n\ttask2 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID2\",\n\t\tGroupUUID: groupUUID,\n\t}\n\n\t// Cleanup before the test\n\tbackend.PurgeState(task1.UUID)\n\tbackend.PurgeState(task2.UUID)\n\tbackend.PurgeGroupMeta(groupUUID)\n\n\tgroupCompleted, err := backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"redis: nil\", err.Error())\n\t}\n\n\tbackend.InitGroup(groupUUID, []string{task1.UUID, task2.UUID})\n\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"redis: nil\", err.Error())\n\t}\n\n\tbackend.SetStatePending(task1)\n\tbackend.SetStateStarted(task2)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\ttaskResults := []*tasks.TaskResult{new(tasks.TaskResult)}\n\tbackend.SetStateStarted(task1)\n\tbackend.SetStateSuccess(task2, taskResults)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.SetStateFailure(task1, \"Some error\")\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, groupCompleted)\n\t}\n}\n\nfunc TestGetStateGR(t *testing.T) {\n\tbackend := getRedisG()\n\tif backend == nil {\n\t\tt.Skip()\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend.PurgeState(\"testTaskUUID\")\n\n\tvar (\n\t\ttaskState *tasks.TaskState\n\t\terr       error\n\t)\n\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Equal(t, \"redis: nil\", err.Error())\n\tassert.Nil(t, taskState)\n\n\t//Pending State\n\tbackend.SetStatePending(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tcreatedAt := taskState.CreatedAt\n\n\t//Received State\n\tbackend.SetStateReceived(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\n\t//Started State\n\tbackend.SetStateStarted(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\n\t//Success State\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  \"float64\",\n\t\t\tValue: 2,\n\t\t},\n\t}\n\tbackend.SetStateSuccess(signature, taskResults)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\tassert.NotNil(t, taskState.Results)\n}\n\nfunc TestPurgeStateGR(t *testing.T) {\n\tbackend := getRedisG()\n\tif backend == nil {\n\t\tt.Skip()\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend.SetStatePending(signature)\n\ttaskState, err := backend.GetState(signature.UUID)\n\tassert.NotNil(t, taskState)\n\tassert.NoError(t, err)\n\n\tbackend.PurgeState(taskState.TaskUUID)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Nil(t, taskState)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "v2/backends/redis/redis.go",
    "content": "package redis\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-redsync/redsync/v4\"\n\tredsyncredis \"github.com/go-redsync/redsync/v4/redis/redigo\"\n\t\"github.com/gomodule/redigo/redis\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// Backend represents a Redis result backend\ntype Backend struct {\n\tcommon.Backend\n\thost     string\n\tusername string\n\tpassword string\n\tdb       int\n\tpool     *redis.Pool\n\t// If set, path to a socket file overrides hostname\n\tsocketPath string\n\tredsync    *redsync.Redsync\n\tredisOnce  sync.Once\n\tcommon.RedisConnector\n}\n\n// New creates Backend instance\nfunc New(cnf *config.Config, host, username, password, socketPath string, db int) iface.Backend {\n\treturn &Backend{\n\t\tBackend:    common.NewBackend(cnf),\n\t\thost:       host,\n\t\tdb:         db,\n\t\tusername:   username,\n\t\tpassword:   password,\n\t\tsocketPath: socketPath,\n\t}\n}\n\n// InitGroup creates and saves a group meta data object\nfunc (b *Backend) InitGroup(groupUUID string, taskUUIDs []string) error {\n\tgroupMeta := &tasks.GroupMeta{\n\t\tGroupUUID: groupUUID,\n\t\tTaskUUIDs: taskUUIDs,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n\n\tencoded, err := json.Marshal(groupMeta)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconn := b.open()\n\tdefer conn.Close()\n\n\texpiration := int64(b.getExpiration().Seconds())\n\t_, err = conn.Do(\"SET\", groupUUID, encoded, \"EX\", expiration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// GroupCompleted returns true if all tasks in a group finished\nfunc (b *Backend) GroupCompleted(groupUUID string, groupTaskCount int) (bool, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tgroupMeta, err := b.getGroupMeta(conn, groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\ttaskStates, err := b.getStates(conn, groupMeta.TaskUUIDs...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar countSuccessTasks = 0\n\tfor _, taskState := range taskStates {\n\t\tif taskState.IsCompleted() {\n\t\t\tcountSuccessTasks++\n\t\t}\n\t}\n\n\treturn countSuccessTasks == groupTaskCount, nil\n}\n\n// GroupTaskStates returns states of all tasks in the group\nfunc (b *Backend) GroupTaskStates(groupUUID string, groupTaskCount int) ([]*tasks.TaskState, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tgroupMeta, err := b.getGroupMeta(conn, groupUUID)\n\tif err != nil {\n\t\treturn []*tasks.TaskState{}, err\n\t}\n\n\treturn b.getStates(conn, groupMeta.TaskUUIDs...)\n}\n\n// TriggerChord flags chord as triggered in the backend storage to make sure\n// chord is never trigerred multiple times. Returns a boolean flag to indicate\n// whether the worker should trigger chord (true) or no if it has been triggered\n// already (false)\nfunc (b *Backend) TriggerChord(groupUUID string) (bool, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tm := b.redsync.NewMutex(\"TriggerChordMutex\")\n\tif err := m.Lock(); err != nil {\n\t\treturn false, err\n\t}\n\tdefer m.Unlock()\n\n\tgroupMeta, err := b.getGroupMeta(conn, groupUUID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Chord has already been triggered, return false (should not trigger again)\n\tif groupMeta.ChordTriggered {\n\t\treturn false, nil\n\t}\n\n\t// Set flag to true\n\tgroupMeta.ChordTriggered = true\n\n\t// Update the group meta\n\tencoded, err := json.Marshal(&groupMeta)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\texpiration := int64(b.getExpiration().Seconds())\n\t_, err = conn.Do(\"SET\", groupUUID, encoded, \"EX\", expiration)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (b *Backend) mergeNewTaskState(conn redis.Conn, newState *tasks.TaskState) {\n\tstate, err := b.getState(conn, newState.TaskUUID)\n\tif err == nil {\n\t\tnewState.CreatedAt = state.CreatedAt\n\t\tnewState.TaskName = state.TaskName\n\t}\n}\n\n// SetStatePending updates task state to PENDING\nfunc (b *Backend) SetStatePending(signature *tasks.Signature) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewPendingTaskState(signature)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateReceived updates task state to RECEIVED\nfunc (b *Backend) SetStateReceived(signature *tasks.Signature) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewReceivedTaskState(signature)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateStarted updates task state to STARTED\nfunc (b *Backend) SetStateStarted(signature *tasks.Signature) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewStartedTaskState(signature)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateRetry updates task state to RETRY\nfunc (b *Backend) SetStateRetry(signature *tasks.Signature) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewRetryTaskState(signature)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateSuccess updates task state to SUCCESS\nfunc (b *Backend) SetStateSuccess(signature *tasks.Signature, results []*tasks.TaskResult) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewSuccessTaskState(signature, results)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// SetStateFailure updates task state to FAILURE\nfunc (b *Backend) SetStateFailure(signature *tasks.Signature, err string) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\ttaskState := tasks.NewFailureTaskState(signature, err)\n\tb.mergeNewTaskState(conn, taskState)\n\treturn b.updateState(conn, taskState)\n}\n\n// GetState returns the latest task state\nfunc (b *Backend) GetState(taskUUID string) (*tasks.TaskState, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\treturn b.getState(conn, taskUUID)\n}\n\nfunc (b *Backend) getState(conn redis.Conn, taskUUID string) (*tasks.TaskState, error) {\n\titem, err := redis.Bytes(conn.Do(\"GET\", taskUUID))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstate := new(tasks.TaskState)\n\tdecoder := json.NewDecoder(bytes.NewReader(item))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(state); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn state, nil\n}\n\n// PurgeState deletes stored task state\nfunc (b *Backend) PurgeState(taskUUID string) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\t_, err := conn.Do(\"DEL\", taskUUID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// PurgeGroupMeta deletes stored group meta data\nfunc (b *Backend) PurgeGroupMeta(groupUUID string) error {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\t_, err := conn.Do(\"DEL\", groupUUID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getGroupMeta retrieves group meta data, convenience function to avoid repetition\nfunc (b *Backend) getGroupMeta(conn redis.Conn, groupUUID string) (*tasks.GroupMeta, error) {\n\n\titem, err := redis.Bytes(conn.Do(\"GET\", groupUUID))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroupMeta := new(tasks.GroupMeta)\n\tdecoder := json.NewDecoder(bytes.NewReader(item))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(groupMeta); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn groupMeta, nil\n}\n\n// getStates returns multiple task states\nfunc (b *Backend) getStates(conn redis.Conn, taskUUIDs ...string) ([]*tasks.TaskState, error) {\n\ttaskStates := make([]*tasks.TaskState, len(taskUUIDs))\n\n\t// conn.Do requires []interface{}... can't pass []string unfortunately\n\ttaskUUIDInterfaces := make([]interface{}, len(taskUUIDs))\n\tfor i, taskUUID := range taskUUIDs {\n\t\ttaskUUIDInterfaces[i] = interface{}(taskUUID)\n\t}\n\n\treply, err := redis.Values(conn.Do(\"MGET\", taskUUIDInterfaces...))\n\tif err != nil {\n\t\treturn taskStates, err\n\t}\n\n\tfor i, value := range reply {\n\t\tstateBytes, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn taskStates, fmt.Errorf(\"Expected byte array, instead got: %v\", value)\n\t\t}\n\n\t\ttaskState := new(tasks.TaskState)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(stateBytes))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(taskState); err != nil {\n\t\t\tlog.ERROR.Print(err)\n\t\t\treturn taskStates, err\n\t\t}\n\n\t\ttaskStates[i] = taskState\n\t}\n\n\treturn taskStates, nil\n}\n\n// updateState saves current task state\nfunc (b *Backend) updateState(conn redis.Conn, taskState *tasks.TaskState) error {\n\tencoded, err := json.Marshal(taskState)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texpiration := int64(b.getExpiration().Seconds())\n\t_, err = conn.Do(\"SET\", taskState.TaskUUID, encoded, \"EX\", expiration)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getExpiration returns expiration for a stored task state\nfunc (b *Backend) getExpiration() time.Duration {\n\texpiresIn := b.GetConfig().ResultsExpireIn\n\tif expiresIn == 0 {\n\t\t// expire results after 1 hour by default\n\t\texpiresIn = config.DefaultResultsExpireIn\n\t}\n\n\treturn time.Duration(expiresIn) * time.Second\n}\n\n// open returns or creates instance of Redis connection\nfunc (b *Backend) open() redis.Conn {\n\tb.redisOnce.Do(func() {\n\t\tb.pool = b.NewPool(b.socketPath, b.host, b.username, b.password, b.db, b.GetConfig().Redis, b.GetConfig().TLSConfig)\n\t\tb.redsync = redsync.New(redsyncredis.NewPool(b.pool))\n\t})\n\treturn b.pool.Get()\n}\n"
  },
  {
    "path": "v2/backends/redis/redis_test.go",
    "content": "package redis_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/redis\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGroupCompleted(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tredisUsername := os.Getenv(\"REDIS_USER\")\n\tredisPassword := os.Getenv(\"REDIS_PASSWORD\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\tgroupUUID := \"testGroupUUID\"\n\ttask1 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID1\",\n\t\tGroupUUID: groupUUID,\n\t}\n\ttask2 := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID2\",\n\t\tGroupUUID: groupUUID,\n\t}\n\n\tbackend := redis.New(new(config.Config), redisURL, redisUsername, redisPassword, \"\", 0)\n\n\t// Cleanup before the test\n\tbackend.PurgeState(task1.UUID)\n\tbackend.PurgeState(task2.UUID)\n\tbackend.PurgeGroupMeta(groupUUID)\n\n\tgroupCompleted, err := backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"redigo: nil returned\", err.Error())\n\t}\n\n\tbackend.InitGroup(groupUUID, []string{task1.UUID, task2.UUID})\n\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.Error(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t\tassert.Equal(t, \"Expected byte array, instead got: <nil>\", err.Error())\n\t}\n\n\tbackend.SetStatePending(task1)\n\tbackend.SetStateStarted(task2)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\ttaskResults := []*tasks.TaskResult{new(tasks.TaskResult)}\n\tbackend.SetStateStarted(task1)\n\tbackend.SetStateSuccess(task2, taskResults)\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.False(t, groupCompleted)\n\t}\n\n\tbackend.SetStateFailure(task1, \"Some error\")\n\tgroupCompleted, err = backend.GroupCompleted(groupUUID, 2)\n\tif assert.NoError(t, err) {\n\t\tassert.True(t, groupCompleted)\n\t}\n}\n\nfunc TestGetState(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tredisUsername := os.Getenv(\"REDIS_USER\")\n\tredisPassword := os.Getenv(\"REDIS_PASSWORD\")\n\tif redisURL == \"\" {\n\t\treturn\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := redis.New(new(config.Config), redisURL, redisUsername, redisPassword, \"\", 0)\n\n\tbackend.PurgeState(\"testTaskUUID\")\n\n\tvar (\n\t\ttaskState *tasks.TaskState\n\t\terr       error\n\t)\n\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Equal(t, \"redigo: nil returned\", err.Error())\n\tassert.Nil(t, taskState)\n\n\t//Pending State\n\tbackend.SetStatePending(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tcreatedAt := taskState.CreatedAt\n\n\t//Received State\n\tbackend.SetStateReceived(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\n\t//Started State\n\tbackend.SetStateStarted(signature)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\n\t//Success State\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  \"float64\",\n\t\t\tValue: 2,\n\t\t},\n\t}\n\tbackend.SetStateSuccess(signature, taskResults)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.NoError(t, err)\n\tassert.Equal(t, signature.Name, taskState.TaskName)\n\tassert.Equal(t, createdAt, taskState.CreatedAt)\n\tassert.NotNil(t, taskState.Results)\n}\n\nfunc TestPurgeState(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tredisUsername := os.Getenv(\"REDIS_USER\")\n\tredisPassword := os.Getenv(\"REDIS_PASSWORD\")\n\tif redisURL == \"\" {\n\t\treturn\n\t}\n\n\tsignature := &tasks.Signature{\n\t\tUUID:      \"testTaskUUID\",\n\t\tGroupUUID: \"testGroupUUID\",\n\t}\n\n\tbackend := redis.New(new(config.Config), redisURL, redisUsername, redisPassword, \"\", 0)\n\n\tbackend.SetStatePending(signature)\n\ttaskState, err := backend.GetState(signature.UUID)\n\tassert.NotNil(t, taskState)\n\tassert.NoError(t, err)\n\n\tbackend.PurgeState(taskState.TaskUUID)\n\ttaskState, err = backend.GetState(signature.UUID)\n\tassert.Nil(t, taskState)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "v2/backends/result/async_result.go",
    "content": "package result\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/iface\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\nvar (\n\t// ErrBackendNotConfigured ...\n\tErrBackendNotConfigured = errors.New(\"Result backend not configured\")\n\t// ErrTimeoutReached ...\n\tErrTimeoutReached = errors.New(\"Timeout reached\")\n)\n\n// AsyncResult represents a task result\ntype AsyncResult struct {\n\tSignature *tasks.Signature\n\ttaskState *tasks.TaskState\n\tbackend   iface.Backend\n}\n\n// ChordAsyncResult represents a result of a chord\ntype ChordAsyncResult struct {\n\tgroupAsyncResults []*AsyncResult\n\tchordAsyncResult  *AsyncResult\n\tbackend           iface.Backend\n}\n\n// ChainAsyncResult represents a result of a chain of tasks\ntype ChainAsyncResult struct {\n\tasyncResults []*AsyncResult\n\tbackend      iface.Backend\n}\n\n// NewAsyncResult creates AsyncResult instance\nfunc NewAsyncResult(signature *tasks.Signature, backend iface.Backend) *AsyncResult {\n\treturn &AsyncResult{\n\t\tSignature: signature,\n\t\ttaskState: new(tasks.TaskState),\n\t\tbackend:   backend,\n\t}\n}\n\n// NewChordAsyncResult creates ChordAsyncResult instance\nfunc NewChordAsyncResult(groupTasks []*tasks.Signature, chordCallback *tasks.Signature, backend iface.Backend) *ChordAsyncResult {\n\tasyncResults := make([]*AsyncResult, len(groupTasks))\n\tfor i, task := range groupTasks {\n\t\tasyncResults[i] = NewAsyncResult(task, backend)\n\t}\n\treturn &ChordAsyncResult{\n\t\tgroupAsyncResults: asyncResults,\n\t\tchordAsyncResult:  NewAsyncResult(chordCallback, backend),\n\t\tbackend:           backend,\n\t}\n}\n\n// NewChainAsyncResult creates ChainAsyncResult instance\nfunc NewChainAsyncResult(tasks []*tasks.Signature, backend iface.Backend) *ChainAsyncResult {\n\tasyncResults := make([]*AsyncResult, len(tasks))\n\tfor i, task := range tasks {\n\t\tasyncResults[i] = NewAsyncResult(task, backend)\n\t}\n\treturn &ChainAsyncResult{\n\t\tasyncResults: asyncResults,\n\t\tbackend:      backend,\n\t}\n}\n\n// Touch the state and don't wait\nfunc (asyncResult *AsyncResult) Touch() ([]reflect.Value, error) {\n\tif asyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tasyncResult.GetState()\n\n\t// Purge state if we are using AMQP backend\n\tif asyncResult.backend.IsAMQP() && asyncResult.taskState.IsCompleted() {\n\t\tasyncResult.backend.PurgeState(asyncResult.taskState.TaskUUID)\n\t}\n\n\tif asyncResult.taskState.IsFailure() {\n\t\treturn nil, errors.New(asyncResult.taskState.Error)\n\t}\n\n\tif asyncResult.taskState.IsSuccess() {\n\t\treturn tasks.ReflectTaskResults(asyncResult.taskState.Results)\n\t}\n\n\treturn nil, nil\n}\n\n// Get returns task results (synchronous blocking call)\nfunc (asyncResult *AsyncResult) Get(sleepDuration time.Duration) ([]reflect.Value, error) {\n\tfor {\n\t\tresults, err := asyncResult.Touch()\n\n\t\tif results == nil && err == nil {\n\t\t\ttime.Sleep(sleepDuration)\n\t\t} else {\n\t\t\treturn results, err\n\t\t}\n\t}\n}\n\n// GetWithTimeout returns task results with a timeout (synchronous blocking call)\nfunc (asyncResult *AsyncResult) GetWithTimeout(timeoutDuration, sleepDuration time.Duration) ([]reflect.Value, error) {\n\ttimeout := time.NewTimer(timeoutDuration)\n\n\tfor {\n\t\tselect {\n\t\tcase <-timeout.C:\n\t\t\treturn nil, ErrTimeoutReached\n\t\tdefault:\n\t\t\tresults, err := asyncResult.Touch()\n\n\t\t\tif results == nil && err == nil {\n\t\t\t\ttime.Sleep(sleepDuration)\n\t\t\t} else {\n\t\t\t\treturn results, err\n\t\t\t}\n\t\t}\n\t}\n}\n\n// GetState returns latest task state\nfunc (asyncResult *AsyncResult) GetState() *tasks.TaskState {\n\tif asyncResult.taskState.IsCompleted() {\n\t\treturn asyncResult.taskState\n\t}\n\n\ttaskState, err := asyncResult.backend.GetState(asyncResult.Signature.UUID)\n\tif err == nil {\n\t\tasyncResult.taskState = taskState\n\t}\n\n\treturn asyncResult.taskState\n}\n\n// Get returns results of a chain of tasks (synchronous blocking call)\nfunc (chainAsyncResult *ChainAsyncResult) Get(sleepDuration time.Duration) ([]reflect.Value, error) {\n\tif chainAsyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tvar (\n\t\tresults []reflect.Value\n\t\terr     error\n\t)\n\n\tfor _, asyncResult := range chainAsyncResult.asyncResults {\n\t\tresults, err = asyncResult.Get(sleepDuration)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn results, err\n}\n\n// Get returns result of a chord (synchronous blocking call)\nfunc (chordAsyncResult *ChordAsyncResult) Get(sleepDuration time.Duration) ([]reflect.Value, error) {\n\tif chordAsyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tvar err error\n\tfor _, asyncResult := range chordAsyncResult.groupAsyncResults {\n\t\t_, err = asyncResult.Get(sleepDuration)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn chordAsyncResult.chordAsyncResult.Get(sleepDuration)\n}\n\n// GetWithTimeout returns results of a chain of tasks with timeout (synchronous blocking call)\nfunc (chainAsyncResult *ChainAsyncResult) GetWithTimeout(timeoutDuration, sleepDuration time.Duration) ([]reflect.Value, error) {\n\tif chainAsyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tvar (\n\t\tresults []reflect.Value\n\t\terr     error\n\t)\n\n\ttimeout := time.NewTimer(timeoutDuration)\n\tln := len(chainAsyncResult.asyncResults)\n\tlastResult := chainAsyncResult.asyncResults[ln-1]\n\n\tfor {\n\t\tselect {\n\t\tcase <-timeout.C:\n\t\t\treturn nil, ErrTimeoutReached\n\t\tdefault:\n\n\t\t\tfor _, asyncResult := range chainAsyncResult.asyncResults {\n\t\t\t\t_, err = asyncResult.Touch()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults, err = lastResult.Touch()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif results != nil {\n\t\t\t\treturn results, err\n\t\t\t}\n\t\t\ttime.Sleep(sleepDuration)\n\t\t}\n\t}\n}\n\n// GetWithTimeout returns result of a chord with a timeout (synchronous blocking call)\nfunc (chordAsyncResult *ChordAsyncResult) GetWithTimeout(timeoutDuration, sleepDuration time.Duration) ([]reflect.Value, error) {\n\tif chordAsyncResult.backend == nil {\n\t\treturn nil, ErrBackendNotConfigured\n\t}\n\n\tvar (\n\t\tresults []reflect.Value\n\t\terr     error\n\t)\n\n\ttimeout := time.NewTimer(timeoutDuration)\n\tfor {\n\t\tselect {\n\t\tcase <-timeout.C:\n\t\t\treturn nil, ErrTimeoutReached\n\t\tdefault:\n\t\t\tfor _, asyncResult := range chordAsyncResult.groupAsyncResults {\n\t\t\t\t_, errcur := asyncResult.Touch()\n\t\t\t\tif errcur != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults, err = chordAsyncResult.chordAsyncResult.Touch()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\tif results != nil {\n\t\t\t\treturn results, err\n\t\t\t}\n\t\t\ttime.Sleep(sleepDuration)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "v2/brokers/amqp/amqp.go",
    "content": "package amqp\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\ntype AMQPConnection struct {\n\tqueueName    string\n\tconnection   *amqp.Connection\n\tchannel      *amqp.Channel\n\tqueue        amqp.Queue\n\tconfirmation <-chan amqp.Confirmation\n\terrorchan    <-chan *amqp.Error\n\tcleanup      chan struct{}\n}\n\n// Broker represents an AMQP broker\ntype Broker struct {\n\tcommon.Broker\n\tcommon.AMQPConnector\n\tprocessingWG sync.WaitGroup // use wait group to make sure task processing completes on interrupt signal\n\n\tconnections      map[string]*AMQPConnection\n\tconnectionsMutex sync.RWMutex\n}\n\n// New creates new Broker instance\nfunc New(cnf *config.Config) iface.Broker {\n\treturn &Broker{Broker: common.NewBroker(cnf), AMQPConnector: common.AMQPConnector{}, connections: make(map[string]*AMQPConnection)}\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\n\tqueueName := taskProcessor.CustomQueue()\n\tif queueName == \"\" {\n\t\tqueueName = b.GetConfig().DefaultQueue\n\t}\n\n\tconn, channel, queue, _, amqpCloseChan, err := b.Connect(\n\t\tb.GetConfig().Broker,\n\t\tb.GetConfig().MultipleBrokerSeparator,\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\tqueueName,                       // queue name\n\t\ttrue,                            // queue durable\n\t\tfalse,                           // queue delete when unused\n\t\tb.GetConfig().AMQP.BindingKey,   // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueDeclareArgs), // queue declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueBindingArgs), // queue binding args\n\t)\n\tif err != nil {\n\t\tb.GetRetryFunc()(b.GetRetryStopChan())\n\t\treturn b.GetRetry(), err\n\t}\n\tdefer b.Close(channel, conn)\n\n\tif err = channel.Qos(\n\t\tb.GetConfig().AMQP.PrefetchCount,\n\t\t0,     // prefetch size\n\t\tfalse, // global\n\t); err != nil {\n\t\treturn b.GetRetry(), fmt.Errorf(\"Channel qos error: %s\", err)\n\t}\n\n\tdeliveries, err := channel.Consume(\n\t\tqueue.Name,  // queue\n\t\tconsumerTag, // consumer tag\n\t\tfalse,       // auto-ack\n\t\tfalse,       // exclusive\n\t\tfalse,       // no-local\n\t\tfalse,       // no-wait\n\t\tnil,         // arguments\n\t)\n\tif err != nil {\n\t\treturn b.GetRetry(), fmt.Errorf(\"Queue consume error: %s\", err)\n\t}\n\n\tlog.INFO.Print(\"[*] Waiting for messages. To exit press CTRL+C\")\n\n\tif err := b.consume(deliveries, concurrency, taskProcessor, amqpCloseChan); err != nil {\n\t\treturn b.GetRetry(), err\n\t}\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *Broker) StopConsuming() {\n\tb.Broker.StopConsuming()\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n}\n\n// GetOrOpenConnection will return a connection on a particular queue name. Open connections\n// are saved to avoid having to reopen connection for multiple queues\nfunc (b *Broker) GetOrOpenConnection(queueName string, queueBindingKey string, exchangeDeclareArgs, queueDeclareArgs, queueBindingArgs amqp.Table) (*AMQPConnection, error) {\n\tvar err error\n\n\tb.connectionsMutex.Lock()\n\tdefer b.connectionsMutex.Unlock()\n\n\tconn, ok := b.connections[queueName]\n\tif !ok {\n\t\tconn = &AMQPConnection{\n\t\t\tqueueName: queueName,\n\t\t\tcleanup:   make(chan struct{}),\n\t\t}\n\t\tconn.connection, conn.channel, conn.queue, conn.confirmation, conn.errorchan, err = b.Connect(\n\t\t\tb.GetConfig().Broker,\n\t\t\tb.GetConfig().MultipleBrokerSeparator,\n\t\t\tb.GetConfig().TLSConfig,\n\t\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\t\tqueueName,                       // queue name\n\t\t\ttrue,                            // queue durable\n\t\t\tfalse,                           // queue delete when unused\n\t\t\tqueueBindingKey,                 // queue binding key\n\t\t\texchangeDeclareArgs,             // exchange declare args\n\t\t\tqueueDeclareArgs,                // queue declare args\n\t\t\tqueueBindingArgs,                // queue binding args\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Failed to connect to queue %s: %s\", queueName, err)\n\t\t}\n\n\t\t// Reconnect to the channel if it disconnects/errors out\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase err = <-conn.errorchan:\n\t\t\t\tlog.INFO.Printf(\"Error occurred on queue: %s. Reconnecting\", queueName)\n\t\t\t\tb.connectionsMutex.Lock()\n\t\t\t\tdelete(b.connections, queueName)\n\t\t\t\tb.connectionsMutex.Unlock()\n\t\t\t\t_, err := b.GetOrOpenConnection(queueName, queueBindingKey, exchangeDeclareArgs, queueDeclareArgs, queueBindingArgs)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.ERROR.Printf(\"Failed to reopen queue: %s.\", queueName)\n\t\t\t\t}\n\t\t\tcase <-conn.cleanup:\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\t\tb.connections[queueName] = conn\n\t}\n\treturn conn, nil\n}\n\nfunc (b *Broker) CloseConnections() error {\n\tb.connectionsMutex.Lock()\n\tdefer b.connectionsMutex.Unlock()\n\n\tfor key, conn := range b.connections {\n\t\tif err := b.Close(conn.channel, conn.connection); err != nil {\n\t\t\tlog.ERROR.Print(\"Failed to close channel\")\n\t\t\treturn nil\n\t\t}\n\t\tclose(conn.cleanup)\n\t\tdelete(b.connections, key)\n\t}\n\treturn nil\n}\n\n// Publish places a new message on the default queue\nfunc (b *Broker) Publish(ctx context.Context, signature *tasks.Signature) error {\n\t// Adjust routing key (this decides which queue the message will be published to)\n\tb.AdjustRoutingKey(signature)\n\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// delay the task\n\tif signature.ETA != nil {\n\t\tnow := time.Now().UTC()\n\n\t\tif signature.ETA.After(now) {\n\t\t\tdelayMs := int64(signature.ETA.Sub(now) / time.Millisecond)\n\n\t\t\treturn b.delay(signature, delayMs)\n\t\t}\n\t}\n\n\tqueue := b.GetConfig().DefaultQueue\n\tbindingKey := b.GetConfig().AMQP.BindingKey // queue binding key\n\tif b.isDirectExchange() {\n\t\tqueue = signature.RoutingKey\n\t\tbindingKey = signature.RoutingKey\n\t}\n\n\tconnection, err := b.GetOrOpenConnection(\n\t\tqueue,\n\t\tbindingKey, // queue binding key\n\t\tnil,        // exchange declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueDeclareArgs), // queue declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueBindingArgs), // queue binding args\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to get a connection for queue %s: %w\", queue, err)\n\t}\n\n\tchannel := connection.channel\n\tconfirmsChan := connection.confirmation\n\n\tif err := channel.Publish(\n\t\tb.GetConfig().AMQP.Exchange, // exchange name\n\t\tsignature.RoutingKey,        // routing key\n\t\tfalse,                       // mandatory\n\t\tfalse,                       // immediate\n\t\tamqp.Publishing{\n\t\t\tHeaders:      amqp.Table(signature.Headers),\n\t\t\tContentType:  \"application/json\",\n\t\t\tBody:         msg,\n\t\t\tPriority:     signature.Priority,\n\t\t\tDeliveryMode: amqp.Persistent,\n\t\t},\n\t); err != nil {\n\t\treturn fmt.Errorf(\"Failed to publish task: %w\", err)\n\t}\n\n\tconfirmed := <-confirmsChan\n\n\tif confirmed.Ack {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"Failed delivery of delivery tag: %v\", confirmed.DeliveryTag)\n}\n\n// consume takes delivered messages from the channel and manages a worker pool\n// to process tasks concurrently\nfunc (b *Broker) consume(deliveries <-chan amqp.Delivery, concurrency int, taskProcessor iface.TaskProcessor, amqpCloseChan <-chan *amqp.Error) error {\n\tpool := make(chan struct{}, concurrency)\n\n\t// initialize worker pool with maxWorkers workers\n\tgo func() {\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tpool <- struct{}{}\n\t\t}\n\t}()\n\n\t// make channel with a capacity makes it become a buffered channel so that a worker which wants to\n\t// push an error to `errorsChan` doesn't need to be blocked while the for-loop is blocked waiting\n\t// a worker, that is, it avoids a possible deadlock\n\terrorsChan := make(chan error, 1)\n\n\tfor {\n\t\tselect {\n\t\tcase amqpErr := <-amqpCloseChan:\n\t\t\treturn amqpErr\n\t\tcase err := <-errorsChan:\n\t\t\treturn err\n\t\tcase d := <-deliveries:\n\t\t\tif concurrency > 0 {\n\t\t\t\t// get worker from pool (blocks until one is available)\n\t\t\t\t<-pool\n\t\t\t}\n\n\t\t\tb.processingWG.Add(1)\n\n\t\t\t// Consume the task inside a gotourine so multiple tasks\n\t\t\t// can be processed concurrently\n\t\t\tgo func() {\n\t\t\t\tif err := b.consumeOne(d, taskProcessor, true); err != nil {\n\t\t\t\t\terrorsChan <- err\n\t\t\t\t}\n\n\t\t\t\tb.processingWG.Done()\n\n\t\t\t\tif concurrency > 0 {\n\t\t\t\t\t// give worker back to pool\n\t\t\t\t\tpool <- struct{}{}\n\t\t\t\t}\n\t\t\t}()\n\t\tcase <-b.GetStopChan():\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// consumeOne processes a single message using TaskProcessor\nfunc (b *Broker) consumeOne(delivery amqp.Delivery, taskProcessor iface.TaskProcessor, ack bool) error {\n\tif len(delivery.Body) == 0 {\n\t\tdelivery.Nack(true, false)                     // multiple, requeue\n\t\treturn errors.New(\"Received an empty message\") // RabbitMQ down?\n\t}\n\n\tvar multiple, requeue = false, false\n\n\t// Unmarshal message body into signature struct\n\tsignature := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewReader(delivery.Body))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(signature); err != nil {\n\t\tdelivery.Nack(multiple, requeue)\n\t\treturn errs.NewErrCouldNotUnmarshalTaskSignature(delivery.Body, err)\n\t}\n\n\t// If the task is not registered, we nack it and requeue,\n\t// there might be different workers for processing specific tasks\n\tif !b.IsTaskRegistered(signature.Name) {\n\t\tif !signature.IgnoreWhenTaskNotRegistered {\n\t\t\trequeue = true\n\t\t}\n\t\tlog.INFO.Printf(\"Task not registered with this worker. Requeuing: %t with message: %s\", requeue, delivery.Body)\n\t\tdelivery.Nack(multiple, requeue)\n\t\treturn nil\n\t}\n\n\tlog.DEBUG.Printf(\"Received new message: %s\", delivery.Body)\n\n\terr := taskProcessor.Process(signature)\n\tif ack {\n\t\tdelivery.Ack(multiple)\n\t}\n\treturn err\n}\n\n// delay a task by delayDuration miliseconds, the way it works is a new queue\n// is created without any consumers, the message is then published to this queue\n// with appropriate ttl expiration headers, after the expiration, it is sent to\n// the proper queue with consumers\nfunc (b *Broker) delay(signature *tasks.Signature, delayMs int64) error {\n\tif delayMs <= 0 {\n\t\treturn errors.New(\"Cannot delay task by 0ms\")\n\t}\n\n\tmessage, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tqueueName := b.GetConfig().AMQP.DelayedQueue\n\tdeclareQueueArgs := amqp.Table{\n\t\t// Exchange where to send messages after TTL expiration.\n\t\t\"x-dead-letter-exchange\": b.GetConfig().AMQP.Exchange,\n\t\t// Routing key which use when resending expired messages.\n\t\t\"x-dead-letter-routing-key\": signature.RoutingKey,\n\t}\n\tmessageProperties := amqp.Publishing{\n\t\tHeaders:      amqp.Table(signature.Headers),\n\t\tContentType:  \"application/json\",\n\t\tBody:         message,\n\t\tDeliveryMode: amqp.Persistent,\n\t\tExpiration:   fmt.Sprint(delayMs),\n\t}\n\n\tif queueName == \"\" {\n\t\t// It's necessary to redeclare the queue each time (to zero its TTL timer).\n\t\tqueueName = fmt.Sprintf(\n\t\t\t\"delay.%d.%s.%s\",\n\t\t\tdelayMs, // delay duration in mileseconds\n\t\t\tb.GetConfig().AMQP.Exchange,\n\t\t\tsignature.RoutingKey, // routing key\n\t\t)\n\t\tdeclareQueueArgs = amqp.Table{\n\t\t\t// Exchange where to send messages after TTL expiration.\n\t\t\t\"x-dead-letter-exchange\": b.GetConfig().AMQP.Exchange,\n\t\t\t// Routing key which use when resending expired messages.\n\t\t\t\"x-dead-letter-routing-key\": signature.RoutingKey,\n\t\t\t// Time in milliseconds\n\t\t\t// after that message will expire and be sent to destination.\n\t\t\t\"x-message-ttl\": delayMs,\n\t\t\t// Time after that the queue will be deleted.\n\t\t\t\"x-expires\": delayMs * 2,\n\t\t}\n\t\tmessageProperties = amqp.Publishing{\n\t\t\tHeaders:      amqp.Table(signature.Headers),\n\t\t\tContentType:  \"application/json\",\n\t\t\tBody:         message,\n\t\t\tDeliveryMode: amqp.Persistent,\n\t\t}\n\t}\n\n\tconn, channel, _, _, _, err := b.Connect(\n\t\tb.GetConfig().Broker,\n\t\tb.GetConfig().MultipleBrokerSeparator,\n\t\tb.GetConfig().TLSConfig,\n\t\tb.GetConfig().AMQP.Exchange,     // exchange name\n\t\tb.GetConfig().AMQP.ExchangeType, // exchange type\n\t\tqueueName,                       // queue name\n\t\ttrue,                            // queue durable\n\t\tb.GetConfig().AMQP.AutoDelete,   // queue delete when unused\n\t\tqueueName,                       // queue binding key\n\t\tnil,                             // exchange declare args\n\t\tdeclareQueueArgs,                // queue declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueBindingArgs), // queue binding args\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer b.Close(channel, conn)\n\n\tif err := channel.Publish(\n\t\tb.GetConfig().AMQP.Exchange, // exchange\n\t\tqueueName,                   // routing key\n\t\tfalse,                       // mandatory\n\t\tfalse,                       // immediate\n\t\tmessageProperties,\n\t); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (b *Broker) isDirectExchange() bool {\n\treturn b.GetConfig().AMQP != nil && b.GetConfig().AMQP.ExchangeType == \"direct\"\n}\n\n// AdjustRoutingKey makes sure the routing key is correct.\n// If the routing key is an empty string:\n// a) set it to binding key for direct exchange type\n// b) set it to default queue name\nfunc (b *Broker) AdjustRoutingKey(s *tasks.Signature) {\n\tif s.RoutingKey != \"\" {\n\t\treturn\n\t}\n\n\tif b.isDirectExchange() {\n\t\t// The routing algorithm behind a direct exchange is simple - a message goes\n\t\t// to the queues whose binding key exactly matches the routing key of the message.\n\t\ts.RoutingKey = b.GetConfig().AMQP.BindingKey\n\t\treturn\n\t}\n\n\ts.RoutingKey = b.GetConfig().DefaultQueue\n}\n\n// Helper type for GetPendingTasks to accumulate signatures\ntype sigDumper struct {\n\tcustomQueue string\n\tSignatures  []*tasks.Signature\n}\n\nfunc (s *sigDumper) Process(sig *tasks.Signature) error {\n\ts.Signatures = append(s.Signatures, sig)\n\treturn nil\n}\n\nfunc (s *sigDumper) CustomQueue() string {\n\treturn s.customQueue\n}\n\nfunc (_ *sigDumper) PreConsumeHandler() bool {\n\treturn true\n}\n\nfunc (b *Broker) GetPendingTasks(queue string) ([]*tasks.Signature, error) {\n\tif queue == \"\" {\n\t\tqueue = b.GetConfig().DefaultQueue\n\t}\n\n\tbindingKey := b.GetConfig().AMQP.BindingKey // queue binding key\n\tconn, err := b.GetOrOpenConnection(\n\t\tqueue,\n\t\tbindingKey, // queue binding key\n\t\tnil,        // exchange declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueDeclareArgs), // queue declare args\n\t\tamqp.Table(b.GetConfig().AMQP.QueueBindingArgs), // queue binding args\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to get a connection for queue %s: %w\", queue, err)\n\t}\n\n\tchannel := conn.channel\n\tqueueInfo, err := channel.QueueInspect(queue)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to get info for queue %s: %w\", queue, err)\n\t}\n\n\tvar tag uint64\n\tdefer channel.Nack(tag, true, true) // multiple, requeue\n\n\tdumper := &sigDumper{customQueue: queue}\n\tfor i := 0; i < queueInfo.Messages; i++ {\n\t\td, _, err := channel.Get(queue, false)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Failed to get from queue: %w\", err)\n\t\t}\n\t\ttag = d.DeliveryTag\n\t\tb.consumeOne(d, dumper, false)\n\t}\n\n\treturn dumper.Signatures, nil\n}\n"
  },
  {
    "path": "v2/brokers/amqp/amqp_concurrence_test.go",
    "content": "package amqp\n\nimport (\n\t\"fmt\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype doNothingProcessor struct{}\n\nfunc (_ doNothingProcessor) Process(signature *tasks.Signature) error {\n\treturn fmt.Errorf(\"failed\")\n}\n\nfunc (_ doNothingProcessor) CustomQueue() string {\n\treturn \"oops\"\n}\n\nfunc (_ doNothingProcessor) PreConsumeHandler() bool {\n\treturn true\n}\n\nfunc TestConsume(t *testing.T) {\n\tvar (\n\t\tiBroker    iface.Broker\n\t\tdeliveries = make(chan amqp.Delivery, 3)\n\t\tcloseChan  chan *amqp.Error\n\t\tprocessor  doNothingProcessor\n\t)\n\n\tt.Run(\"with deliveries more than the number of concurrency\", func(t *testing.T) {\n\t\tiBroker = New(&config.Config{})\n\t\tbroker, _ := iBroker.(*Broker)\n\t\terrChan := make(chan error)\n\n\t\t// simulate that there are too much deliveries\n\t\tgo func() {\n\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\tdeliveries <- amqp.Delivery{} // broker.consumeOne() will complain this error: Received an empty message\n\t\t\t}\n\t\t}()\n\n\t\tgo func() {\n\t\t\terr := broker.consume(deliveries, 2, processor, closeChan)\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}()\n\n\t\tselect {\n\t\tcase <-errChan:\n\t\tcase <-time.After(1 * time.Second):\n\t\t\tt.Error(\"Maybe deadlock\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "v2/brokers/amqp/amqp_test.go",
    "content": "package amqp_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/brokers/amqp\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestAdjustRoutingKey(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\ts      *tasks.Signature\n\t\tbroker iface.Broker\n\t)\n\n\tt.Run(\"with routing and binding keys\", func(t *testing.T) {\n\t\ts := &tasks.Signature{RoutingKey: \"routing_key\"}\n\t\tbroker = amqp.New(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t\tAMQP: &config.AMQPConfig{\n\t\t\t\tExchangeType: \"direct\",\n\t\t\t\tBindingKey:   \"binding_key\",\n\t\t\t},\n\t\t})\n\t\tbroker.AdjustRoutingKey(s)\n\t\tassert.Equal(t, \"routing_key\", s.RoutingKey)\n\t})\n\n\tt.Run(\"with binding key\", func(t *testing.T) {\n\t\ts = new(tasks.Signature)\n\t\tbroker = amqp.New(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t\tAMQP: &config.AMQPConfig{\n\t\t\t\tExchangeType: \"direct\",\n\t\t\t\tBindingKey:   \"binding_key\",\n\t\t\t},\n\t\t})\n\t\tbroker.AdjustRoutingKey(s)\n\t\tassert.Equal(t, \"binding_key\", s.RoutingKey)\n\t})\n}\n"
  },
  {
    "path": "v2/brokers/eager/eager.go",
    "content": "package eager\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// Broker represents an \"eager\" in-memory broker\ntype Broker struct {\n\tworker iface.TaskProcessor\n\tcommon.Broker\n}\n\n// New creates new Broker instance\nfunc New() iface.Broker {\n\treturn new(Broker)\n}\n\n// Mode interface with methods specific for this broker\ntype Mode interface {\n\tAssignWorker(p iface.TaskProcessor)\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (eagerBroker *Broker) StartConsuming(consumerTag string, concurrency int, p iface.TaskProcessor) (bool, error) {\n\treturn true, nil\n}\n\n// StopConsuming quits the loop\nfunc (eagerBroker *Broker) StopConsuming() {\n\t// do nothing\n}\n\n// Publish places a new message on the default queue\nfunc (eagerBroker *Broker) Publish(ctx context.Context, task *tasks.Signature) error {\n\tif eagerBroker.worker == nil {\n\t\treturn errors.New(\"worker is not assigned in eager-mode\")\n\t}\n\n\t// faking the behavior to marshal input into json\n\t// and unmarshal it back\n\tmessage, err := json.Marshal(task)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tsignature := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewReader(message))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(signature); err != nil {\n\t\treturn fmt.Errorf(\"JSON unmarshal error: %s\", err)\n\t}\n\n\t// blocking call to the task directly\n\treturn eagerBroker.worker.Process(signature)\n}\n\n// AssignWorker assigns a worker to the eager broker\nfunc (eagerBroker *Broker) AssignWorker(w iface.TaskProcessor) {\n\teagerBroker.worker = w\n}\n"
  },
  {
    "path": "v2/brokers/errs/errors.go",
    "content": "package errs\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// ErrCouldNotUnmarshalTaskSignature ...\ntype ErrCouldNotUnmarshalTaskSignature struct {\n\tmsg    []byte\n\treason string\n}\n\n// Error implements the error interface\nfunc (e ErrCouldNotUnmarshalTaskSignature) Error() string {\n\treturn fmt.Sprintf(\"Could not unmarshal '%s' into a task signature: %v\", e.msg, e.reason)\n}\n\n// NewErrCouldNotUnmarshalTaskSignature returns new ErrCouldNotUnmarshalTaskSignature instance\nfunc NewErrCouldNotUnmarshalTaskSignature(msg []byte, err error) ErrCouldNotUnmarshalTaskSignature {\n\treturn ErrCouldNotUnmarshalTaskSignature{msg: msg, reason: err.Error()}\n}\n\n// ErrConsumerStopped indicates that the operation is now illegal because of the consumer being stopped.\nvar ErrConsumerStopped = errors.New(\"the server has been stopped\")\n\n// ErrStopTaskDeletion indicates that the task should not be deleted from source after task failure\nvar ErrStopTaskDeletion = errors.New(\"task should not be deleted\")\n"
  },
  {
    "path": "v2/brokers/gcppubsub/gcp_pubsub.go",
    "content": "package gcppubsub\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"cloud.google.com/go/pubsub\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// Broker represents an Google Cloud Pub/Sub broker\ntype Broker struct {\n\tcommon.Broker\n\n\tservice          *pubsub.Client\n\tsubscriptionName string\n\tMaxExtension     time.Duration\n\n\tstopDone chan struct{}\n}\n\n// New creates new Broker instance\nfunc New(cnf *config.Config, projectID, subscriptionName string) (iface.Broker, error) {\n\tb := &Broker{Broker: common.NewBroker(cnf), stopDone: make(chan struct{})}\n\tb.subscriptionName = subscriptionName\n\n\tctx := context.Background()\n\n\tif cnf.GCPPubSub != nil {\n\t\tb.MaxExtension = cnf.GCPPubSub.MaxExtension\n\t}\n\n\tif cnf.GCPPubSub != nil && cnf.GCPPubSub.Client != nil {\n\t\tb.service = cnf.GCPPubSub.Client\n\t} else {\n\t\tpubsubClient, err := pubsub.NewClient(ctx, projectID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb.service = pubsubClient\n\t\tcnf.GCPPubSub = &config.GCPPubSubConfig{\n\t\t\tClient: pubsubClient,\n\t\t}\n\t}\n\n\t// Validate topic exists\n\tdefaultQueue := b.GetConfig().DefaultQueue\n\ttopic := b.service.Topic(defaultQueue)\n\tdefer topic.Stop()\n\n\ttopicExists, err := topic.Exists(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !topicExists {\n\t\treturn nil, fmt.Errorf(\"topic does not exist, instead got %s\", defaultQueue)\n\t}\n\n\t// Validate subscription exists\n\tsub := b.service.Subscription(b.subscriptionName)\n\n\tif b.MaxExtension != 0 {\n\t\tsub.ReceiveSettings.MaxExtension = b.MaxExtension\n\t}\n\n\tsubscriptionExists, err := sub.Exists(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !subscriptionExists {\n\t\treturn nil, fmt.Errorf(\"subscription does not exist, instead got %s\", b.subscriptionName)\n\t}\n\n\treturn b, nil\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\n\tsub := b.service.Subscription(b.subscriptionName)\n\n\tif b.MaxExtension != 0 {\n\t\tsub.ReceiveSettings.MaxExtension = b.MaxExtension\n\t}\n\n\tsub.ReceiveSettings.NumGoroutines = concurrency\n\tlog.INFO.Print(\"[*] Waiting for messages. To exit press CTRL+C\")\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func() {\n\t\t<-b.GetStopChan()\n\t\tcancel()\n\t}()\n\n\tfor {\n\t\terr := sub.Receive(ctx, func(_ctx context.Context, msg *pubsub.Message) {\n\t\t\tb.consumeOne(msg, taskProcessor)\n\t\t})\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tlog.ERROR.Printf(\"Error when receiving messages. Error: %v\", err)\n\t\tcontinue\n\t}\n\n\tclose(b.stopDone)\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *Broker) StopConsuming() {\n\tb.Broker.StopConsuming()\n\n\t// Waiting for any tasks being processed to finish\n\t<-b.stopDone\n}\n\n// Publish places a new message on the default queue or the queue pointed to\n// by the routing key\nfunc (b *Broker) Publish(ctx context.Context, signature *tasks.Signature) error {\n\t// Adjust routing key (this decides which queue the message will be published to)\n\tb.AdjustRoutingKey(signature)\n\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\ttopic := b.service.Topic(signature.RoutingKey)\n\tdefer topic.Stop()\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// delay the task\n\tif signature.ETA != nil {\n\t\tnow := time.Now().UTC()\n\n\t\tif signature.ETA.After(now) {\n\t\t\ttopic.PublishSettings.DelayThreshold = signature.ETA.Sub(now)\n\t\t}\n\t}\n\n\tresult := topic.Publish(ctx, &pubsub.Message{\n\t\tData: msg,\n\t})\n\n\tid, err := result.Get(ctx)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Error when sending a message: %v\", err)\n\t\treturn err\n\t}\n\n\tlog.INFO.Printf(\"Sending a message successfully, server-generated message ID %v\", id)\n\treturn nil\n}\n\n// consumeOne processes a single message using TaskProcessor\nfunc (b *Broker) consumeOne(delivery *pubsub.Message, taskProcessor iface.TaskProcessor) {\n\tif len(delivery.Data) == 0 {\n\t\tdelivery.Nack()\n\t\tlog.ERROR.Printf(\"received an empty message, the delivery was %v\", delivery)\n\t}\n\n\tsig := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewBuffer(delivery.Data))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(sig); err != nil {\n\t\tdelivery.Nack()\n\t\tlog.ERROR.Printf(\"unmarshal error. the delivery is %v\", delivery)\n\t}\n\n\t// If the task is not registered return an error\n\t// and leave the message in the queue\n\tif !b.IsTaskRegistered(sig.Name) {\n\t\tdelivery.Nack()\n\t\tlog.ERROR.Printf(\"task %s is not registered\", sig.Name)\n\t}\n\n\terr := taskProcessor.Process(sig)\n\tif err != nil {\n\t\tdelivery.Nack()\n\t\tlog.ERROR.Printf(\"Failed process of task\", err)\n\t}\n\n\t// Call Ack() after successfully consuming and processing the message\n\tdelivery.Ack()\n}\n"
  },
  {
    "path": "v2/brokers/iface/interfaces.go",
    "content": "package iface\n\nimport (\n\t\"context\"\n\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// Broker - a common interface for all brokers\ntype Broker interface {\n\tGetConfig() *config.Config\n\tSetRegisteredTaskNames(names []string)\n\tIsTaskRegistered(name string) bool\n\tStartConsuming(consumerTag string, concurrency int, p TaskProcessor) (bool, error)\n\tStopConsuming()\n\tPublish(ctx context.Context, task *tasks.Signature) error\n\tGetPendingTasks(queue string) ([]*tasks.Signature, error)\n\tGetDelayedTasks() ([]*tasks.Signature, error)\n\tAdjustRoutingKey(s *tasks.Signature)\n}\n\n// TaskProcessor - can process a delivered task\n// This will probably always be a worker instance\ntype TaskProcessor interface {\n\tProcess(signature *tasks.Signature) error\n\tCustomQueue() string\n\tPreConsumeHandler() bool\n}\n"
  },
  {
    "path": "v2/brokers/iface/sqs/api.go",
    "content": "package sqs\n\nimport (\n\t\"context\"\n\n\t\"github.com/aws/aws-sdk-go-v2/service/sqs\"\n)\n\n// API is an interface for SQS API\ntype API interface {\n\tSendMessage(ctx context.Context, params *sqs.SendMessageInput, optFns ...func(*sqs.Options)) (*sqs.SendMessageOutput, error)\n\tReceiveMessage(ctx context.Context, params *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error)\n\tDeleteMessage(ctx context.Context, params *sqs.DeleteMessageInput, optFns ...func(*sqs.Options)) (*sqs.DeleteMessageOutput, error)\n\tChangeMessageVisibility(ctx context.Context, params *sqs.ChangeMessageVisibilityInput, optFns ...func(*sqs.Options)) (*sqs.ChangeMessageVisibilityOutput, error)\n}\n"
  },
  {
    "path": "v2/brokers/package.go",
    "content": "package brokers\n"
  },
  {
    "path": "v2/brokers/redis/goredis.go",
    "content": "package redis\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-redsync/redsync/v4\"\n\t\"github.com/redis/go-redis/v9\"\n\n\t\"github.com/RichardKnop/machinery/v2/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\n// BrokerGR represents a Redis broker\ntype BrokerGR struct {\n\tcommon.Broker\n\trclient      redis.UniversalClient\n\tconsumingWG  sync.WaitGroup // wait group to make sure whole consumption completes\n\tprocessingWG sync.WaitGroup // use wait group to make sure task processing completes\n\tdelayedWG    sync.WaitGroup\n\t// If set, path to a socket file overrides hostname\n\tsocketPath           string\n\tredsync              *redsync.Redsync\n\tredisOnce            sync.Once\n\tredisDelayedTasksKey string\n}\n\n// NewGR creates new Broker instance\nfunc NewGR(cnf *config.Config, addrs []string, db int) iface.Broker {\n\tb := &BrokerGR{Broker: common.NewBroker(cnf)}\n\n\tvar password string\n\tvar username string\n\tparts := strings.Split(addrs[0], \"@\")\n\tif len(parts) >= 2 {\n\t\t// with password\n\t\toptions := strings.SplitN(strings.Join(parts[:len(parts)-1], \"@\"), \":\", 2)\n\t\tif len(options) >= 2 {\n\t\t\tusername = options[0]\n\t\t\tpassword = options[1]\n\t\t} else {\n\t\t\tpassword = options[0]\n\t\t}\n\n\t\taddrs[0] = parts[len(parts)-1] // addr is the last one without @\n\t}\n\n\tropt := &redis.UniversalOptions{\n\t\tAddrs:    addrs,\n\t\tDB:       db,\n\t\tPassword: password,\n\t\tUsername: username,\n\t}\n\tif cnf.Redis != nil {\n\t\tropt.MasterName = cnf.Redis.MasterName\n\t}\n\tif cnf.TLSConfig != nil {\n\t\tropt.TLSConfig = cnf.TLSConfig\n\t}\n\n\tif cnf.Redis != nil && cnf.Redis.SentinelPassword != \"\" {\n\t\tropt.SentinelPassword = cnf.Redis.SentinelPassword\n\t}\n\n\tif cnf.Redis != nil && cnf.Redis.ClusterEnabled {\n\t\tb.rclient = redis.NewClusterClient(ropt.Cluster())\n\t} else {\n\t\tb.rclient = redis.NewUniversalClient(ropt)\n\t}\n\tif cnf.Redis != nil && cnf.Redis.DelayedTasksKey != \"\" {\n\t\tb.redisDelayedTasksKey = cnf.Redis.DelayedTasksKey\n\t} else {\n\t\tb.redisDelayedTasksKey = defaultRedisDelayedTasksKey\n\t}\n\treturn b\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *BrokerGR) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.consumingWG.Add(1)\n\tdefer b.consumingWG.Done()\n\n\tif concurrency < 1 {\n\t\tconcurrency = runtime.NumCPU() * 2\n\t}\n\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\n\t// Ping the server to make sure connection is live\n\t_, err := b.rclient.Ping(context.Background()).Result()\n\tif err != nil {\n\t\tb.GetRetryFunc()(b.GetRetryStopChan())\n\n\t\t// Return err if retry is still true.\n\t\t// If retry is false, broker.StopConsuming() has been called and\n\t\t// therefore Redis might have been stopped. Return nil exit\n\t\t// StartConsuming()\n\t\tif b.GetRetry() {\n\t\t\treturn b.GetRetry(), err\n\t\t}\n\t\treturn b.GetRetry(), errs.ErrConsumerStopped\n\t}\n\n\t// Channel to which we will push tasks ready for processing by worker\n\tdeliveries := make(chan []byte, concurrency)\n\tpool := make(chan struct{}, concurrency)\n\n\t// initialize worker pool with maxWorkers workers\n\tfor i := 0; i < concurrency; i++ {\n\t\tpool <- struct{}{}\n\t}\n\n\t// A receiving goroutine keeps popping messages from the queue by BLPOP\n\t// If the message is valid and can be unmarshaled into a proper structure\n\t// we send it to the deliveries channel\n\tgo func() {\n\n\t\tlog.INFO.Print(\"[*] Waiting for messages. To exit press CTRL+C\")\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.GetStopChan():\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tcase <-pool:\n\t\t\t\ttask, _ := b.nextTask(getQueueGR(b.GetConfig(), taskProcessor))\n\t\t\t\t//TODO: should this error be ignored?\n\t\t\t\tif len(task) > 0 {\n\t\t\t\t\tdeliveries <- task\n\t\t\t\t}\n\n\t\t\t\tpool <- struct{}{}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// A goroutine to watch for delayed tasks and push them to deliveries\n\t// channel for consumption by the worker\n\tb.delayedWG.Add(1)\n\tgo func() {\n\t\tdefer b.delayedWG.Done()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.GetStopChan():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\ttask, err := b.nextDelayedTask(b.redisDelayedTasksKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tsignature := new(tasks.Signature)\n\t\t\t\tdecoder := json.NewDecoder(bytes.NewReader(task))\n\t\t\t\tdecoder.UseNumber()\n\t\t\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\t\t\tlog.ERROR.Print(errs.NewErrCouldNotUnmarshalTaskSignature(task, err))\n\t\t\t\t}\n\n\t\t\t\tif err := b.Publish(context.Background(), signature); err != nil {\n\t\t\t\t\tlog.ERROR.Print(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tif err := b.consume(deliveries, concurrency, taskProcessor); err != nil {\n\t\treturn b.GetRetry(), err\n\t}\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *BrokerGR) StopConsuming() {\n\tb.Broker.StopConsuming()\n\t// Waiting for the delayed tasks goroutine to have stopped\n\tb.delayedWG.Wait()\n\t// Waiting for consumption to finish\n\tb.consumingWG.Wait()\n\n\tb.rclient.Close()\n}\n\n// Publish places a new message on the default queue\nfunc (b *BrokerGR) Publish(ctx context.Context, signature *tasks.Signature) error {\n\t// Adjust routing key (this decides which queue the message will be published to)\n\tb.Broker.AdjustRoutingKey(signature)\n\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// delay the task\n\tif signature.ETA != nil {\n\t\tnow := time.Now().UTC()\n\n\t\tif signature.ETA.After(now) {\n\t\t\tscore := signature.ETA.UnixNano()\n\t\t\terr = b.rclient.ZAdd(context.Background(), b.redisDelayedTasksKey, redis.Z{Score: float64(score), Member: msg}).Err()\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = b.rclient.RPush(context.Background(), signature.RoutingKey, msg).Err()\n\treturn err\n}\n\n// GetPendingTasks returns a slice of task signatures waiting in the queue\nfunc (b *BrokerGR) GetPendingTasks(queue string) ([]*tasks.Signature, error) {\n\n\tif queue == \"\" {\n\t\tqueue = b.GetConfig().DefaultQueue\n\t}\n\tresults, err := b.rclient.LRange(context.Background(), queue, 0, -1).Result()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttaskSignatures := make([]*tasks.Signature, len(results))\n\tfor i, result := range results {\n\t\tsignature := new(tasks.Signature)\n\t\tdecoder := json.NewDecoder(strings.NewReader(result))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskSignatures[i] = signature\n\t}\n\treturn taskSignatures, nil\n}\n\n// GetDelayedTasks returns a slice of task signatures that are scheduled, but not yet in the queue\nfunc (b *BrokerGR) GetDelayedTasks() ([]*tasks.Signature, error) {\n\tresults, err := b.rclient.ZRange(context.Background(), b.redisDelayedTasksKey, 0, -1).Result()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttaskSignatures := make([]*tasks.Signature, len(results))\n\tfor i, result := range results {\n\t\tsignature := new(tasks.Signature)\n\t\tdecoder := json.NewDecoder(strings.NewReader(result))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskSignatures[i] = signature\n\t}\n\treturn taskSignatures, nil\n}\n\n// consume takes delivered messages from the channel and manages a worker pool\n// to process tasks concurrently\nfunc (b *BrokerGR) consume(deliveries <-chan []byte, concurrency int, taskProcessor iface.TaskProcessor) error {\n\terrorsChan := make(chan error, concurrency*2)\n\tpool := make(chan struct{}, concurrency)\n\n\t// init pool for Worker tasks execution, as many slots as Worker concurrency param\n\tgo func() {\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tpool <- struct{}{}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase err := <-errorsChan:\n\t\t\treturn err\n\t\tcase d, open := <-deliveries:\n\t\t\tif !open {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif concurrency > 0 {\n\t\t\t\t// get execution slot from pool (blocks until one is available)\n\t\t\t\t<-pool\n\t\t\t}\n\n\t\t\tb.processingWG.Add(1)\n\n\t\t\t// Consume the task inside a goroutine so multiple tasks\n\t\t\t// can be processed concurrently\n\t\t\tgo func() {\n\t\t\t\tif err := b.consumeOne(d, taskProcessor); err != nil {\n\t\t\t\t\terrorsChan <- err\n\t\t\t\t}\n\n\t\t\t\tb.processingWG.Done()\n\n\t\t\t\tif concurrency > 0 {\n\t\t\t\t\t// give slot back to pool\n\t\t\t\t\tpool <- struct{}{}\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n}\n\n// consumeOne processes a single message using TaskProcessor\nfunc (b *BrokerGR) consumeOne(delivery []byte, taskProcessor iface.TaskProcessor) error {\n\tsignature := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewReader(delivery))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(signature); err != nil {\n\t\treturn errs.NewErrCouldNotUnmarshalTaskSignature(delivery, err)\n\t}\n\n\t// If the task is not registered, we requeue it,\n\t// there might be different workers for processing specific tasks\n\tif !b.IsTaskRegistered(signature.Name) {\n\t\tif signature.IgnoreWhenTaskNotRegistered {\n\t\t\treturn nil\n\t\t}\n\t\tlog.INFO.Printf(\"Task not registered with this worker. Requeuing message: %s\", delivery)\n\n\t\tb.rclient.RPush(context.Background(), getQueueGR(b.GetConfig(), taskProcessor), delivery)\n\t\treturn nil\n\t}\n\n\tlog.DEBUG.Printf(\"Received new message: %s\", delivery)\n\n\treturn taskProcessor.Process(signature)\n}\n\n// nextTask pops next available task from the default queue\nfunc (b *BrokerGR) nextTask(queue string) (result []byte, err error) {\n\n\tpollPeriodMilliseconds := 1000 // default poll period for normal tasks\n\tif b.GetConfig().Redis != nil {\n\t\tconfiguredPollPeriod := b.GetConfig().Redis.NormalTasksPollPeriod\n\t\tif configuredPollPeriod > 0 {\n\t\t\tpollPeriodMilliseconds = configuredPollPeriod\n\t\t}\n\t}\n\tpollPeriod := time.Duration(pollPeriodMilliseconds) * time.Millisecond\n\n\titems, err := b.rclient.BLPop(context.Background(), pollPeriod, queue).Result()\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\n\t// items[0] - the name of the key where an element was popped\n\t// items[1] - the value of the popped element\n\tif len(items) != 2 {\n\t\treturn []byte{}, redis.Nil\n\t}\n\n\tresult = []byte(items[1])\n\n\treturn result, nil\n}\n\n// nextDelayedTask pops a value from the ZSET key using WATCH/MULTI/EXEC commands.\nfunc (b *BrokerGR) nextDelayedTask(key string) (result []byte, err error) {\n\n\t//pipe := b.rclient.Pipeline()\n\t//\n\t//defer func() {\n\t//\t// Return connection to normal state on error.\n\t//\t// https://redis.io/commands/discard\n\t//\tif err != nil {\n\t//\t\tpipe.Discard()\n\t//\t}\n\t//}()\n\n\tvar (\n\t\titems []string\n\t)\n\n\tpollPeriod := 500 // default poll period for delayed tasks\n\tif b.GetConfig().Redis != nil {\n\t\tconfiguredPollPeriod := b.GetConfig().Redis.DelayedTasksPollPeriod\n\t\t// the default period is 0, which bombards redis with requests, despite\n\t\t// our intention of doing the opposite\n\t\tif configuredPollPeriod > 0 {\n\t\t\tpollPeriod = configuredPollPeriod\n\t\t}\n\t}\n\n\tfor {\n\t\t// Space out queries to ZSET so we don't bombard redis\n\t\t// server with relentless ZRANGEBYSCOREs\n\t\ttime.Sleep(time.Duration(pollPeriod) * time.Millisecond)\n\t\twatchFunc := func(tx *redis.Tx) error {\n\n\t\t\tnow := time.Now().UTC().UnixNano()\n\n\t\t\t// https://redis.io/commands/zrangebyscore\n\t\t\tctx := context.Background()\n\t\t\titems, err = tx.ZRevRangeByScore(ctx, key, &redis.ZRangeBy{\n\t\t\t\tMin: \"0\", Max: strconv.FormatInt(now, 10), Offset: 0, Count: 1,\n\t\t\t}).Result()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif len(items) != 1 {\n\t\t\t\treturn redis.Nil\n\t\t\t}\n\n\t\t\t// only return the first zrange value if there are no other changes in this key\n\t\t\t// to make sure a delayed task would only be consumed once\n\t\t\t_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {\n\t\t\t\tpipe.ZRem(ctx, key, items[0])\n\t\t\t\tresult = []byte(items[0])\n\t\t\t\treturn nil\n\t\t\t})\n\n\t\t\treturn err\n\t\t}\n\n\t\tif err = b.rclient.Watch(context.Background(), watchFunc, key); err != nil {\n\t\t\treturn\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc getQueueGR(config *config.Config, taskProcessor iface.TaskProcessor) string {\n\tcustomQueue := taskProcessor.CustomQueue()\n\tif customQueue == \"\" {\n\t\treturn config.DefaultQueue\n\t}\n\treturn customQueue\n}\n"
  },
  {
    "path": "v2/brokers/redis/redis.go",
    "content": "package redis\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-redsync/redsync/v4\"\n\tredsyncredis \"github.com/go-redsync/redsync/v4/redis/redigo\"\n\t\"github.com/gomodule/redigo/redis\"\n\n\t\"github.com/RichardKnop/machinery/v2/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\nconst defaultRedisDelayedTasksKey = \"delayed_tasks\"\n\n// Broker represents a Redis broker\ntype Broker struct {\n\tcommon.Broker\n\tcommon.RedisConnector\n\thost         string\n\tusername     string\n\tpassword     string\n\tdb           int\n\tpool         *redis.Pool\n\tconsumingWG  sync.WaitGroup // wait group to make sure whole consumption completes\n\tprocessingWG sync.WaitGroup // use wait group to make sure task processing completes\n\tdelayedWG    sync.WaitGroup\n\t// If set, path to a socket file overrides hostname\n\tsocketPath           string\n\tredsync              *redsync.Redsync\n\tredisOnce            sync.Once\n\tredisDelayedTasksKey string\n}\n\n// New creates new Broker instance\nfunc New(cnf *config.Config, host, username, password, socketPath string, db int) iface.Broker {\n\tb := &Broker{Broker: common.NewBroker(cnf)}\n\tb.host = host\n\tb.db = db\n\tb.username = username\n\tb.password = password\n\tb.socketPath = socketPath\n\n\tif cnf.Redis != nil && cnf.Redis.DelayedTasksKey != \"\" {\n\t\tb.redisDelayedTasksKey = cnf.Redis.DelayedTasksKey\n\t} else {\n\t\tb.redisDelayedTasksKey = defaultRedisDelayedTasksKey\n\t}\n\n\treturn b\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.consumingWG.Add(1)\n\tdefer b.consumingWG.Done()\n\n\tif concurrency < 1 {\n\t\tconcurrency = runtime.NumCPU() * 2\n\t}\n\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\n\tconn := b.open()\n\tdefer conn.Close()\n\n\t// Ping the server to make sure connection is live\n\t_, err := conn.Do(\"PING\")\n\tif err != nil {\n\t\tb.GetRetryFunc()(b.GetRetryStopChan())\n\n\t\t// Return err if retry is still true.\n\t\t// If retry is false, broker.StopConsuming() has been called and\n\t\t// therefore Redis might have been stopped. Return nil exit\n\t\t// StartConsuming()\n\t\tif b.GetRetry() {\n\t\t\treturn b.GetRetry(), err\n\t\t}\n\t\treturn b.GetRetry(), errs.ErrConsumerStopped\n\t}\n\n\t// Channel to which we will push tasks ready for processing by worker\n\tdeliveries := make(chan []byte, concurrency)\n\tpool := make(chan struct{}, concurrency)\n\tstopConsumer := make(chan struct{})\n\n\t// initialize worker pool with maxWorkers workers\n\tfor i := 0; i < concurrency; i++ {\n\t\tpool <- struct{}{}\n\t}\n\n\t// A receiving goroutine keeps popping messages from the queue by BLPOP\n\t// If the message is valid and can be unmarshaled into a proper structure\n\t// we send it to the deliveries channel\n\tgo func() {\n\n\t\tlog.INFO.Print(\"[*] Waiting for messages. To exit press CTRL+C\")\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.GetStopChan():\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tcase <-stopConsumer:\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tcase <-pool:\n\t\t\t\tselect {\n\t\t\t\tcase <-b.GetStopChan():\n\t\t\t\t\tclose(deliveries)\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t}\n\n\t\t\t\tif taskProcessor.PreConsumeHandler() {\n\t\t\t\t\ttask, _ := b.nextTask(getQueue(b.GetConfig(), taskProcessor))\n\t\t\t\t\t//TODO: should this error be ignored?\n\t\t\t\t\tif len(task) > 0 {\n\t\t\t\t\t\tdeliveries <- task\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tpool <- struct{}{}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// A goroutine to watch for delayed tasks and push them to deliveries\n\t// channel for consumption by the worker\n\tb.delayedWG.Add(1)\n\tgo func() {\n\t\tdefer b.delayedWG.Done()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.GetStopChan():\n\t\t\t\treturn\n\t\t\tcase <-stopConsumer:\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\ttask, err := b.nextDelayedTask(b.redisDelayedTasksKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tsignature := new(tasks.Signature)\n\t\t\t\tdecoder := json.NewDecoder(bytes.NewReader(task))\n\t\t\t\tdecoder.UseNumber()\n\t\t\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\t\t\tlog.ERROR.Print(errs.NewErrCouldNotUnmarshalTaskSignature(task, err))\n\t\t\t\t}\n\n\t\t\t\tif err := b.Publish(context.Background(), signature); err != nil {\n\t\t\t\t\tlog.ERROR.Print(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tif err := b.consume(deliveries, concurrency, taskProcessor, stopConsumer); err != nil {\n\t\treturn b.GetRetry(), err\n\t}\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *Broker) StopConsuming() {\n\tb.Broker.StopConsuming()\n\t// Waiting for the delayed tasks goroutine to have stopped\n\tb.delayedWG.Wait()\n\t// Waiting for consumption to finish\n\tb.consumingWG.Wait()\n\t// Wait for currently processing tasks to finish as well.\n\tb.processingWG.Wait()\n\n\tif b.pool != nil {\n\t\tb.pool.Close()\n\t}\n}\n\n// Publish places a new message on the default queue\nfunc (b *Broker) Publish(ctx context.Context, signature *tasks.Signature) error {\n\t// Adjust routing key (this decides which queue the message will be published to)\n\tb.Broker.AdjustRoutingKey(signature)\n\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\tconn := b.open()\n\tdefer conn.Close()\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// delay the task\n\tif signature.ETA != nil {\n\t\tnow := time.Now().UTC()\n\n\t\tif signature.ETA.After(now) {\n\t\t\tscore := signature.ETA.UnixNano()\n\t\t\t_, err = conn.Do(\"ZADD\", b.redisDelayedTasksKey, score, msg)\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = conn.Do(\"RPUSH\", signature.RoutingKey, msg)\n\treturn err\n}\n\n// GetPendingTasks returns a slice of task signatures waiting in the queue\nfunc (b *Broker) GetPendingTasks(queue string) ([]*tasks.Signature, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tif queue == \"\" {\n\t\tqueue = b.GetConfig().DefaultQueue\n\t}\n\tdataBytes, err := conn.Do(\"LRANGE\", queue, 0, -1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults, err := redis.ByteSlices(dataBytes, err)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttaskSignatures := make([]*tasks.Signature, len(results))\n\tfor i, result := range results {\n\t\tsignature := new(tasks.Signature)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(result))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskSignatures[i] = signature\n\t}\n\treturn taskSignatures, nil\n}\n\n// GetDelayedTasks returns a slice of task signatures that are scheduled, but not yet in the queue\nfunc (b *Broker) GetDelayedTasks() ([]*tasks.Signature, error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tdataBytes, err := conn.Do(\"ZRANGE\", b.redisDelayedTasksKey, 0, -1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults, err := redis.ByteSlices(dataBytes, err)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttaskSignatures := make([]*tasks.Signature, len(results))\n\tfor i, result := range results {\n\t\tsignature := new(tasks.Signature)\n\t\tdecoder := json.NewDecoder(bytes.NewReader(result))\n\t\tdecoder.UseNumber()\n\t\tif err := decoder.Decode(signature); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskSignatures[i] = signature\n\t}\n\treturn taskSignatures, nil\n}\n\n// consume takes delivered messages from the channel and manages a worker pool\n// to process tasks concurrently\nfunc (b *Broker) consume(deliveries <-chan []byte, concurrency int, taskProcessor iface.TaskProcessor, stopConsumer chan struct{}) error {\n\terrorsChan := make(chan error, concurrency*2)\n\tpool := make(chan struct{}, concurrency)\n\n\t// init pool for Worker tasks execution, as many slots as Worker concurrency param\n\tgo func() {\n\t\tfor i := 0; i < concurrency; i++ {\n\t\t\tpool <- struct{}{}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase err := <-errorsChan:\n\t\t\tfor v := range deliveries {\n\t\t\t\tb.requeueMessage(v, taskProcessor)\n\t\t\t}\n\t\t\treturn err\n\t\tcase d, open := <-deliveries:\n\t\t\tif !open {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif concurrency > 0 {\n\t\t\t\t// get execution slot from pool (blocks until one is available)\n\t\t\t\tselect {\n\t\t\t\tcase <-b.GetStopChan():\n\t\t\t\t\tb.requeueMessage(d, taskProcessor)\n\t\t\t\t\tcontinue\n\t\t\t\tcase <-pool:\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tb.processingWG.Add(1)\n\n\t\t\t// Consume the task inside a goroutine so multiple tasks\n\t\t\t// can be processed concurrently\n\t\t\tgo func() {\n\t\t\t\tif err := b.consumeOne(d, taskProcessor); err != nil {\n\t\t\t\t\terrorsChan <- err\n\t\t\t\t}\n\n\t\t\t\tb.processingWG.Done()\n\n\t\t\t\tif concurrency > 0 {\n\t\t\t\t\t// give slot back to pool\n\t\t\t\t\tpool <- struct{}{}\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n}\n\n// consumeOne processes a single message using TaskProcessor\nfunc (b *Broker) consumeOne(delivery []byte, taskProcessor iface.TaskProcessor) error {\n\tsignature := new(tasks.Signature)\n\tdecoder := json.NewDecoder(bytes.NewReader(delivery))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(signature); err != nil {\n\t\treturn errs.NewErrCouldNotUnmarshalTaskSignature(delivery, err)\n\t}\n\n\t// If the task is not registered, we requeue it,\n\t// there might be different workers for processing specific tasks\n\tif !b.IsTaskRegistered(signature.Name) {\n\t\tif signature.IgnoreWhenTaskNotRegistered {\n\t\t\treturn nil\n\t\t}\n\t\tlog.INFO.Printf(\"Task not registered with this worker. Requeuing message: %s\", delivery)\n\t\tb.requeueMessage(delivery, taskProcessor)\n\t\treturn nil\n\t}\n\n\tlog.DEBUG.Printf(\"Received new message: %s\", delivery)\n\n\treturn taskProcessor.Process(signature)\n}\n\n// nextTask pops next available task from the default queue\nfunc (b *Broker) nextTask(queue string) (result []byte, err error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tpollPeriodMilliseconds := 1000 // default poll period for normal tasks\n\tif b.GetConfig().Redis != nil {\n\t\tconfiguredPollPeriod := b.GetConfig().Redis.NormalTasksPollPeriod\n\t\tif configuredPollPeriod > 0 {\n\t\t\tpollPeriodMilliseconds = configuredPollPeriod\n\t\t}\n\t}\n\tpollPeriod := time.Duration(pollPeriodMilliseconds) * time.Millisecond\n\n\t// Issue 548: BLPOP expects an integer timeout expresses in seconds.\n\t// The call will if the value is a float. Convert to integer using\n\t// math.Ceil():\n\t//   math.Ceil(0.0) --> 0 (block indefinitely)\n\t//   math.Ceil(0.2) --> 1 (timeout after 1 second)\n\tpollPeriodSeconds := math.Ceil(pollPeriod.Seconds())\n\n\titems, err := redis.ByteSlices(conn.Do(\"BLPOP\", queue, pollPeriodSeconds))\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\n\t// items[0] - the name of the key where an element was popped\n\t// items[1] - the value of the popped element\n\tif len(items) != 2 {\n\t\treturn []byte{}, redis.ErrNil\n\t}\n\n\tresult = items[1]\n\n\treturn result, nil\n}\n\n// nextDelayedTask pops a value from the ZSET key using WATCH/MULTI/EXEC commands.\n// https://github.com/gomodule/redigo/blob/master/redis/zpop_example_test.go\nfunc (b *Broker) nextDelayedTask(key string) (result []byte, err error) {\n\tconn := b.open()\n\tdefer conn.Close()\n\n\tdefer func() {\n\t\t// Return connection to normal state on error.\n\t\t// https://redis.io/commands/discard\n\t\t// https://redis.io/commands/unwatch\n\t\tif err == redis.ErrNil {\n\t\t\tconn.Do(\"UNWATCH\")\n\t\t} else if err != nil {\n\t\t\tconn.Do(\"DISCARD\")\n\t\t}\n\t}()\n\n\tvar (\n\t\titems [][]byte\n\t\treply interface{}\n\t)\n\n\tpollPeriod := 500 // default poll period for delayed tasks\n\tif b.GetConfig().Redis != nil {\n\t\tconfiguredPollPeriod := b.GetConfig().Redis.DelayedTasksPollPeriod\n\t\t// the default period is 0, which bombards redis with requests, despite\n\t\t// our intention of doing the opposite\n\t\tif configuredPollPeriod > 0 {\n\t\t\tpollPeriod = configuredPollPeriod\n\t\t}\n\t}\n\n\tfor {\n\t\t// Space out queries to ZSET so we don't bombard redis\n\t\t// server with relentless ZRANGEBYSCOREs\n\t\ttime.Sleep(time.Duration(pollPeriod) * time.Millisecond)\n\t\tif _, err = conn.Do(\"WATCH\", key); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tnow := time.Now().UTC().UnixNano()\n\n\t\t// https://redis.io/commands/zrangebyscore\n\t\titems, err = redis.ByteSlices(conn.Do(\n\t\t\t\"ZRANGEBYSCORE\",\n\t\t\tkey,\n\t\t\t0,\n\t\t\tnow,\n\t\t\t\"LIMIT\",\n\t\t\t0,\n\t\t\t1,\n\t\t))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif len(items) != 1 {\n\t\t\terr = redis.ErrNil\n\t\t\treturn\n\t\t}\n\n\t\t_ = conn.Send(\"MULTI\")\n\t\t_ = conn.Send(\"ZREM\", key, items[0])\n\t\treply, err = conn.Do(\"EXEC\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif reply != nil {\n\t\t\tresult = items[0]\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\n// open returns or creates instance of Redis connection\nfunc (b *Broker) open() redis.Conn {\n\tb.redisOnce.Do(func() {\n\t\tb.pool = b.NewPool(b.socketPath, b.host, b.username, b.password, b.db, b.GetConfig().Redis, b.GetConfig().TLSConfig)\n\t\tb.redsync = redsync.New(redsyncredis.NewPool(b.pool))\n\t})\n\n\treturn b.pool.Get()\n}\n\nfunc getQueue(config *config.Config, taskProcessor iface.TaskProcessor) string {\n\tcustomQueue := taskProcessor.CustomQueue()\n\tif customQueue == \"\" {\n\t\treturn config.DefaultQueue\n\t}\n\treturn customQueue\n}\n\nfunc (b *Broker) requeueMessage(delivery []byte, taskProcessor iface.TaskProcessor) {\n\tconn := b.open()\n\tdefer conn.Close()\n\tconn.Do(\"RPUSH\", getQueue(b.GetConfig(), taskProcessor), delivery)\n}\n"
  },
  {
    "path": "v2/brokers/sqs/sqs.go",
    "content": "package sqs\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\tsqsiface \"github.com/RichardKnop/machinery/v2/brokers/iface/sqs\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tawsconfig \"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sqs\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sqs/types\"\n)\n\nconst (\n\tmaxAWSSQSDelay = time.Minute * 15 // Max supported SQS delay is 15 min: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html\n)\n\n// Broker represents a AWS SQS broker\n// There are examples on: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/sqs-example-create-queue.html\ntype Broker struct {\n\tcommon.Broker\n\tprocessingWG      sync.WaitGroup // use wait group to make sure task processing completes on interrupt signal\n\treceivingWG       sync.WaitGroup\n\tstopReceivingChan chan int\n\tservice           sqsiface.API\n\tqueueUrl          *string\n}\n\n// New creates new Broker instance\nfunc New(cnf *config.Config) (iface.Broker, error) {\n\tb := &Broker{Broker: common.NewBroker(cnf)}\n\tif cnf.SQS != nil && cnf.SQS.Client != nil {\n\t\t// Use provided *SQS client\n\t\tb.service = cnf.SQS.Client\n\t} else {\n\t\tcfg, err := awsconfig.LoadDefaultConfig(context.TODO())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: unable to load AWS SDK config: \", err)\n\t\t}\n\n\t\tb.service = sqs.NewFromConfig(cfg)\n\t}\n\n\treturn b, nil\n}\n\n// StartConsuming enters a loop and waits for incoming messages\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) (bool, error) {\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n\tqURL := b.getQueueURL(taskProcessor)\n\t//save it so that it can be used later when attempting to delete task\n\tb.queueUrl = qURL\n\n\tdeliveries := make(chan *sqs.ReceiveMessageOutput, concurrency)\n\tpool := make(chan struct{}, concurrency)\n\n\t// initialize worker pool with maxWorkers workers\n\tfor i := 0; i < concurrency; i++ {\n\t\tpool <- struct{}{}\n\t}\n\tb.stopReceivingChan = make(chan int)\n\tb.receivingWG.Add(1)\n\n\tgo func() {\n\t\tdefer b.receivingWG.Done()\n\n\t\tlog.INFO.Printf(\"[*] Waiting for messages on queue: %s. To exit press CTRL+C\\n\", *qURL)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\t// A way to stop this goroutine from b.StopConsuming\n\t\t\tcase <-b.stopReceivingChan:\n\t\t\t\tclose(deliveries)\n\t\t\t\treturn\n\t\t\tcase <-pool:\n\t\t\t\toutput, err := b.receiveMessage(qURL)\n\t\t\t\tif err == nil && len(output.Messages) > 0 {\n\t\t\t\t\tdeliveries <- output\n\n\t\t\t\t} else {\n\t\t\t\t\t//return back to pool right away\n\t\t\t\t\tpool <- struct{}{}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.ERROR.Printf(\"Queue consume error: %s\", err)\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}()\n\n\tif err := b.consume(deliveries, concurrency, taskProcessor, pool); err != nil {\n\t\treturn b.GetRetry(), err\n\t}\n\n\treturn b.GetRetry(), nil\n}\n\n// StopConsuming quits the loop\nfunc (b *Broker) StopConsuming() {\n\tb.Broker.StopConsuming()\n\n\tb.stopReceiving()\n\n\t// Waiting for any tasks being processed to finish\n\tb.processingWG.Wait()\n\n\t// Waiting for the receiving goroutine to have stopped\n\tb.receivingWG.Wait()\n}\n\n// Publish places a new message on the default queue\nfunc (b *Broker) Publish(ctx context.Context, signature *tasks.Signature) error {\n\tmsg, err := json.Marshal(signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"JSON marshal error: %s\", err)\n\t}\n\n\t// Check that signature.RoutingKey is set, if not switch to DefaultQueue\n\tb.AdjustRoutingKey(signature)\n\n\tMsgInput := &sqs.SendMessageInput{\n\t\tMessageBody: aws.String(string(msg)),\n\t\tQueueUrl:    aws.String(b.GetConfig().Broker + \"/\" + signature.RoutingKey),\n\t}\n\n\t// if this is a fifo queue, there needs to be some additional parameters.\n\tif strings.HasSuffix(signature.RoutingKey, \".fifo\") {\n\t\t// Use Machinery's signature Task UUID as SQS Message Group ID.\n\t\tMsgDedupID := signature.UUID\n\t\tMsgInput.MessageDeduplicationId = aws.String(MsgDedupID)\n\n\t\t// Do not Use Machinery's signature Group UUID as SQS Message Group ID, instead use BrokerMessageGroupId\n\t\tMsgGroupID := signature.BrokerMessageGroupId\n\t\tif MsgGroupID == \"\" {\n\t\t\treturn fmt.Errorf(\"please specify BrokerMessageGroupId attribute for task Signature when submitting a task to FIFO queue\")\n\t\t}\n\t\tMsgInput.MessageGroupId = aws.String(MsgGroupID)\n\t}\n\n\t// Check the ETA signature field, if it is set and it is in the future,\n\t// and is not a fifo queue, set a delay in seconds for the task.\n\tif signature.ETA != nil && !strings.HasSuffix(signature.RoutingKey, \".fifo\") {\n\t\tnow := time.Now().UTC()\n\t\tdelay := signature.ETA.Sub(now)\n\t\tif delay > 0 {\n\t\t\tif delay > maxAWSSQSDelay {\n\t\t\t\treturn errors.New(\"Max AWS SQS delay exceeded\")\n\t\t\t}\n\t\t\tMsgInput.DelaySeconds = int32(delay.Seconds())\n\t\t}\n\t}\n\n\tresult, err := b.service.SendMessage(ctx, MsgInput)\n\n\tif err != nil {\n\t\tlog.ERROR.Printf(\"Error when sending a message: %v\", err)\n\t\treturn err\n\n\t}\n\tlog.INFO.Printf(\"Sending a message successfully, the messageId is %v\", *result.MessageId)\n\treturn nil\n\n}\n\n// consume is a method which keeps consuming deliveries from a channel, until there is an error or a stop signal\nfunc (b *Broker) consume(deliveries <-chan *sqs.ReceiveMessageOutput, concurrency int, taskProcessor iface.TaskProcessor, pool chan struct{}) error {\n\n\terrorsChan := make(chan error)\n\n\tfor {\n\t\twhetherContinue, err := b.consumeDeliveries(deliveries, concurrency, taskProcessor, pool, errorsChan)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif whetherContinue == false {\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// consumeOne is a method consumes a delivery. If a delivery was consumed successfully, it will be deleted from AWS SQS\nfunc (b *Broker) consumeOne(delivery *sqs.ReceiveMessageOutput, taskProcessor iface.TaskProcessor) error {\n\tif len(delivery.Messages) == 0 {\n\t\tlog.ERROR.Printf(\"received an empty message, the delivery was %v\", delivery)\n\t\treturn fmt.Errorf(\"received empty message, the delivery is %v\", delivery)\n\t}\n\n\tif b.GetConfig().SQS.VisibilityHeartBeat {\n\t\tnotify := make(chan struct{})\n\t\tdefer close(notify)\n\n\t\tb.visibilityHeartbeat(delivery, notify)\n\t}\n\n\tif b.GetConfig().SQS.VisibilityHeartBeat {\n\t\tnotify := make(chan struct{})\n\t\tdefer close(notify)\n\n\t\tb.visibilityHeartbeat(delivery, notify)\n\t}\n\n\tsig := new(tasks.Signature)\n\tdecoder := json.NewDecoder(strings.NewReader(*delivery.Messages[0].Body))\n\tdecoder.UseNumber()\n\tif err := decoder.Decode(sig); err != nil {\n\t\tlog.ERROR.Printf(\"unmarshal error. the delivery is %v\", delivery)\n\t\t// if the unmarshal fails, remove the delivery from the queue\n\t\tif delErr := b.deleteOne(delivery); delErr != nil {\n\t\t\tlog.ERROR.Printf(\"error when deleting the delivery. delivery is %v, Error=%s\", delivery, delErr)\n\t\t}\n\t\treturn err\n\t}\n\tif delivery.Messages[0].ReceiptHandle != nil {\n\t\tsig.SQSReceiptHandle = *delivery.Messages[0].ReceiptHandle\n\t}\n\n\t// If the task is not registered return an error\n\t// and leave the message in the queue\n\tif !b.IsTaskRegistered(sig.Name) {\n\t\tif sig.IgnoreWhenTaskNotRegistered {\n\t\t\tif err := b.deleteOne(delivery); err != nil {\n\t\t\t\tlog.ERROR.Printf(\"error when deleting the delivery. delivery is %v, Error=%s\", delivery, err)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"task %s is not registered\", sig.Name)\n\t}\n\n\terr := taskProcessor.Process(sig)\n\tif err != nil {\n\t\t// stop task deletion in case we want to send messages to dlq in sqs\n\t\tif errors.Is(err, errs.ErrStopTaskDeletion) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\t// Delete message after successfully consuming and processing the message\n\tif err = b.deleteOne(delivery); err != nil {\n\t\tlog.ERROR.Printf(\"error when deleting the delivery. delivery is %v, Error=%s\", delivery, err)\n\t}\n\treturn err\n}\n\n// deleteOne is a method delete a delivery from AWS SQS\nfunc (b *Broker) deleteOne(delivery *sqs.ReceiveMessageOutput) error {\n\tqURL := b.defaultQueueURL()\n\t_, err := b.service.DeleteMessage(context.TODO(), &sqs.DeleteMessageInput{\n\t\tQueueUrl:      qURL,\n\t\tReceiptHandle: delivery.Messages[0].ReceiptHandle,\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// defaultQueueURL is a method returns the default queue url\nfunc (b *Broker) defaultQueueURL() *string {\n\tif b.queueUrl != nil {\n\t\treturn b.queueUrl\n\t} else {\n\t\treturn aws.String(b.GetConfig().Broker + \"/\" + b.GetConfig().DefaultQueue)\n\t}\n\n}\n\n// receiveMessage is a method receives a message from specified queue url\nfunc (b *Broker) receiveMessage(qURL *string) (*sqs.ReceiveMessageOutput, error) {\n\tvar waitTimeSeconds int\n\tvar visibilityTimeout *int\n\tif b.GetConfig().SQS != nil {\n\t\twaitTimeSeconds = b.GetConfig().SQS.WaitTimeSeconds\n\t\tvisibilityTimeout = b.GetConfig().SQS.VisibilityTimeout\n\t}\n\n\tinput := &sqs.ReceiveMessageInput{\n\t\tMessageSystemAttributeNames: []types.MessageSystemAttributeName{\n\t\t\ttypes.MessageSystemAttributeNameSentTimestamp,\n\t\t},\n\t\tMessageAttributeNames: []string{\n\t\t\tstring(types.QueueAttributeNameAll),\n\t\t},\n\t\tQueueUrl:            qURL,\n\t\tMaxNumberOfMessages: 1,\n\t\tWaitTimeSeconds:     int32(waitTimeSeconds),\n\t}\n\tif visibilityTimeout != nil {\n\t\tinput.VisibilityTimeout = int32(*visibilityTimeout)\n\t}\n\tresult, err := b.service.ReceiveMessage(context.TODO(), input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn result, err\n}\n\n// initializePool is a method which initializes concurrency pool\nfunc (b *Broker) initializePool(pool chan struct{}, concurrency int) {\n\tfor i := 0; i < concurrency; i++ {\n\t\tpool <- struct{}{}\n\t}\n}\n\n// consumeDeliveries is a method consuming deliveries from deliveries channel\nfunc (b *Broker) consumeDeliveries(deliveries <-chan *sqs.ReceiveMessageOutput, concurrency int, taskProcessor iface.TaskProcessor, pool chan struct{}, errorsChan chan error) (bool, error) {\n\tselect {\n\tcase err := <-errorsChan:\n\t\treturn false, err\n\tcase d := <-deliveries:\n\n\t\tb.processingWG.Add(1)\n\n\t\t// Consume the task inside a goroutine so multiple tasks\n\t\t// can be processed concurrently\n\t\tgo func() {\n\n\t\t\tif err := b.consumeOne(d, taskProcessor); err != nil {\n\t\t\t\terrorsChan <- err\n\t\t\t}\n\n\t\t\tb.processingWG.Done()\n\n\t\t\tif concurrency > 0 {\n\t\t\t\t// give worker back to pool\n\t\t\t\tpool <- struct{}{}\n\t\t\t}\n\t\t}()\n\tcase <-b.GetStopChan():\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\n// continueReceivingMessages is a method returns a continue signal\nfunc (b *Broker) continueReceivingMessages(qURL *string, deliveries chan *sqs.ReceiveMessageOutput) (bool, error) {\n\tselect {\n\t// A way to stop this goroutine from b.StopConsuming\n\tcase <-b.stopReceivingChan:\n\t\treturn false, nil\n\tdefault:\n\t\toutput, err := b.receiveMessage(qURL)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t\tif len(output.Messages) == 0 {\n\t\t\treturn true, nil\n\t\t}\n\t\tgo func() { deliveries <- output }()\n\t}\n\treturn true, nil\n}\n\n// visibilityHeartbeat is a method that sends a heartbeat signal to AWS SQS to keep a message invisible to other consumers while being processed.\nfunc (b *Broker) visibilityHeartbeat(delivery *sqs.ReceiveMessageOutput, notify <-chan struct{}) {\n\tif b.GetConfig().SQS.VisibilityTimeout == nil || *b.GetConfig().SQS.VisibilityTimeout == 0 {\n\t\treturn\n\t}\n\n\tticker := time.NewTicker(time.Duration(*b.GetConfig().SQS.VisibilityTimeout) * 500 * time.Millisecond)\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-notify:\n\t\t\t\tticker.Stop()\n\n\t\t\t\treturn\n\t\t\tcase <-b.stopReceivingChan:\n\t\t\t\tticker.Stop()\n\n\t\t\t\treturn\n\t\t\tcase <-ticker.C:\n\t\t\t\t// Extend the delivery visibility timeout\n\t\t\t\t_, err := b.service.ChangeMessageVisibility(context.TODO(), &sqs.ChangeMessageVisibilityInput{\n\t\t\t\t\tQueueUrl:          b.defaultQueueURL(),\n\t\t\t\t\tReceiptHandle:     delivery.Messages[0].ReceiptHandle,\n\t\t\t\t\tVisibilityTimeout: int32(*b.GetConfig().SQS.VisibilityTimeout),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.ERROR.Printf(\"Error when changing delivery visibility: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// stopReceiving is a method sending a signal to stopReceivingChan\nfunc (b *Broker) stopReceiving() {\n\t// Stop the receiving goroutine\n\tb.stopReceivingChan <- 1\n}\n\n// getQueueURL is a method returns that returns queueURL first by checking if custom queue was set and usign it\n// otherwise using default queueName from config\nfunc (b *Broker) getQueueURL(taskProcessor iface.TaskProcessor) *string {\n\tqueueName := b.GetConfig().DefaultQueue\n\tif taskProcessor.CustomQueue() != \"\" {\n\t\tqueueName = taskProcessor.CustomQueue()\n\t}\n\n\treturn aws.String(b.GetConfig().Broker + \"/\" + queueName)\n}\n"
  },
  {
    "path": "v2/brokers/sqs/sqs_export_test.go",
    "content": "package sqs\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\tsqsiface \"github.com/RichardKnop/machinery/v2/brokers/iface/sqs\"\n\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tawssqs \"github.com/aws/aws-sdk-go-v2/service/sqs\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sqs/types\"\n)\n\nvar (\n\tReceiveMessageOutput *awssqs.ReceiveMessageOutput\n)\n\ntype FakeSQS struct {\n\tsqsiface.API\n}\n\nfunc (f *FakeSQS) SendMessage(context.Context, *awssqs.SendMessageInput, ...func(*awssqs.Options)) (*awssqs.SendMessageOutput, error) {\n\toutput := awssqs.SendMessageOutput{\n\t\tMD5OfMessageAttributes: aws.String(\"d25a6aea97eb8f585bfa92d314504a92\"),\n\t\tMD5OfMessageBody:       aws.String(\"bbdc5fdb8be7251f5c910905db994bab\"),\n\t\tMessageId:              aws.String(\"47f8b355-5115-4b45-b33a-439016400411\"),\n\t}\n\treturn &output, nil\n}\n\nfunc (f *FakeSQS) ReceiveMessage(context.Context, *awssqs.ReceiveMessageInput, ...func(*awssqs.Options)) (*awssqs.ReceiveMessageOutput, error) {\n\treturn ReceiveMessageOutput, nil\n}\n\nfunc (f *FakeSQS) DeleteMessage(context.Context, *awssqs.DeleteMessageInput, ...func(*awssqs.Options)) (*awssqs.DeleteMessageOutput, error) {\n\treturn &awssqs.DeleteMessageOutput{}, nil\n}\n\ntype ErrorSQS struct {\n\tsqsiface.API\n}\n\nfunc (e *ErrorSQS) SendMessage(context.Context, *awssqs.SendMessageInput, ...func(*awssqs.Options)) (*awssqs.SendMessageOutput, error) {\n\terr := errors.New(\"this is an error\")\n\treturn nil, err\n}\n\nfunc (e *ErrorSQS) ReceiveMessage(context.Context, *awssqs.ReceiveMessageInput, ...func(*awssqs.Options)) (*awssqs.ReceiveMessageOutput, error) {\n\terr := errors.New(\"this is an error\")\n\treturn nil, err\n}\n\nfunc (e *ErrorSQS) DeleteMessage(context.Context, *awssqs.DeleteMessageInput, ...func(*awssqs.Options)) (*awssqs.DeleteMessageOutput, error) {\n\terr := errors.New(\"this is an error\")\n\treturn nil, err\n}\n\nfunc init() {\n\t// TODO: chang message body to signature example\n\tmessageBody, _ := json.Marshal(map[string]int{\"apple\": 5, \"lettuce\": 7})\n\tReceiveMessageOutput = &awssqs.ReceiveMessageOutput{\n\t\tMessages: []types.Message{\n\t\t\t{\n\t\t\t\tAttributes: map[string]string{\n\t\t\t\t\t\"SentTimestamp\": \"1512962021537\",\n\t\t\t\t},\n\t\t\t\tBody:                   aws.String(string(messageBody)),\n\t\t\t\tMD5OfBody:              aws.String(\"bbdc5fdb8be7251f5c910905db994bab\"),\n\t\t\t\tMD5OfMessageAttributes: aws.String(\"d25a6aea97eb8f585bfa92d314504a92\"),\n\t\t\t\tMessageAttributes: map[string]types.MessageAttributeValue{\n\t\t\t\t\t\"Title\": {\n\t\t\t\t\t\tDataType:    aws.String(\"String\"),\n\t\t\t\t\t\tStringValue: aws.String(\"The Whistler\"),\n\t\t\t\t\t},\n\t\t\t\t\t\"Author\": {\n\t\t\t\t\t\tDataType:    aws.String(\"String\"),\n\t\t\t\t\t\tStringValue: aws.String(\"John Grisham\"),\n\t\t\t\t\t},\n\t\t\t\t\t\"WeeksOn\": {\n\t\t\t\t\t\tDataType:    aws.String(\"Number\"),\n\t\t\t\t\t\tStringValue: aws.String(\"6\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMessageId:     aws.String(\"47f8b355-5115-4b45-b33a-439016400411\"),\n\t\t\t\tReceiptHandle: aws.String(\"AQEBGhTR/nhq+pDPAunCDgLpwQuCq0JkD2dtv7pAcPF5DA/XaoPAjHfgn/PZ5DeG3YiQdTjCUj+rvFq5b79DTq+hK6r1Niuds02l+jdIk3u2JiL01Dsd203pW1lLUNryd74QAcn462eXzv7/hVDagXTn+KtOzox3X0vmPkCSQkWXWxtc23oa5+5Q7HWDmRm743L0zza1579rQ2R2B0TrdlTMpNsdjQlDmybNu+aDq8bazD/Wew539tIvUyYADuhVyKyS1L2QQuyXll73/DixulPNmvGPRHNoB1GIo+Ex929OHFchXoKonoFJnurX4VNNl1p/Byp2IYBi6nkTRzeJUFCrFq0WMAHKLwuxciezJSlLD7g3bbU8kgEer8+jTz1DBriUlDGsARr0s7mnlsd02cb46K/j+u1oPfA69vIVc0FaRtA=\"),\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc NewTestConfig() *config.Config {\n\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tredisURL = \"eager\"\n\t}\n\tbrokerURL := \"https://sqs.foo.amazonaws.com.cn\"\n\treturn &config.Config{\n\t\tBroker:        brokerURL,\n\t\tDefaultQueue:  \"test_queue\",\n\t\tResultBackend: fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tLock:          fmt.Sprintf(\"redis://%v\", redisURL),\n\t\tSQS: &config.SQSConfig{\n\t\t\tVisibilityTimeout: aws.Int(30),\n\t\t},\n\t}\n}\n\nfunc NewTestBroker(cnf *config.Config) *Broker {\n\n\tvar svc sqsiface.API = new(FakeSQS)\n\n\tif cnf.SQS.Client != nil {\n\t\tsvc = cnf.SQS.Client\n\t}\n\treturn &Broker{\n\t\tBroker:            common.NewBroker(cnf),\n\t\tservice:           svc,\n\t\tprocessingWG:      sync.WaitGroup{},\n\t\treceivingWG:       sync.WaitGroup{},\n\t\tstopReceivingChan: make(chan int),\n\t}\n}\n\nfunc NewTestErrorBroker() *Broker {\n\n\tcnf := NewTestConfig()\n\n\terrSvc := new(ErrorSQS)\n\treturn &Broker{\n\t\tBroker:            common.NewBroker(cnf),\n\t\tservice:           errSvc,\n\t\tprocessingWG:      sync.WaitGroup{},\n\t\treceivingWG:       sync.WaitGroup{},\n\t\tstopReceivingChan: make(chan int),\n\t}\n}\n\nfunc (b *Broker) ConsumeForTest(deliveries <-chan *awssqs.ReceiveMessageOutput, concurrency int, taskProcessor iface.TaskProcessor, pool chan struct{}) error {\n\treturn b.consume(deliveries, concurrency, taskProcessor, pool)\n}\n\nfunc (b *Broker) ConsumeOneForTest(delivery *awssqs.ReceiveMessageOutput, taskProcessor iface.TaskProcessor) error {\n\treturn b.consumeOne(delivery, taskProcessor)\n}\n\nfunc (b *Broker) DeleteOneForTest(delivery *awssqs.ReceiveMessageOutput) error {\n\treturn b.deleteOne(delivery)\n}\n\nfunc (b *Broker) DefaultQueueURLForTest() *string {\n\treturn b.defaultQueueURL()\n}\n\nfunc (b *Broker) ReceiveMessageForTest(qURL *string) (*awssqs.ReceiveMessageOutput, error) {\n\treturn b.receiveMessage(qURL)\n}\n\nfunc (b *Broker) InitializePoolForTest(pool chan struct{}, concurrency int) {\n\tb.initializePool(pool, concurrency)\n}\n\nfunc (b *Broker) ConsumeDeliveriesForTest(deliveries <-chan *awssqs.ReceiveMessageOutput, concurrency int, taskProcessor iface.TaskProcessor, pool chan struct{}, errorsChan chan error) (bool, error) {\n\treturn b.consumeDeliveries(deliveries, concurrency, taskProcessor, pool, errorsChan)\n}\n\nfunc (b *Broker) ContinueReceivingMessagesForTest(qURL *string, deliveries chan *awssqs.ReceiveMessageOutput) (bool, error) {\n\treturn b.continueReceivingMessages(qURL, deliveries)\n}\n\nfunc (b *Broker) StopReceivingForTest() {\n\tb.stopReceiving()\n}\n\nfunc (b *Broker) GetStopReceivingChanForTest() chan int {\n\treturn b.stopReceivingChan\n}\n\nfunc (b *Broker) StartConsumingForTest(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) {\n\tb.Broker.StartConsuming(consumerTag, concurrency, taskProcessor)\n}\n\nfunc (b *Broker) GetRetryFuncForTest() func(chan int) {\n\treturn b.GetRetryFunc()\n}\n\nfunc (b *Broker) GetStopChanForTest() chan int {\n\treturn b.GetStopChan()\n}\n\nfunc (b *Broker) GetRetryStopChanForTest() chan int {\n\treturn b.GetRetryStopChan()\n}\n\nfunc (b *Broker) GetQueueURLForTest(taskProcessor iface.TaskProcessor) *string {\n\treturn b.getQueueURL(taskProcessor)\n}\n\nfunc (b *Broker) GetCustomQueueURL(customQueue string) *string {\n\treturn aws.String(b.GetConfig().Broker + \"/\" + customQueue)\n}\n"
  },
  {
    "path": "v2/brokers/sqs/sqs_test.go",
    "content": "package sqs_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n\teagerbck \"github.com/RichardKnop/machinery/v2/backends/eager\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/eager\"\n\tsqsiface \"github.com/RichardKnop/machinery/v2/brokers/iface/sqs\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/sqs\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\teagerlock \"github.com/RichardKnop/machinery/v2/locks/eager\"\n\t\"github.com/RichardKnop/machinery/v2/retry\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tawssqs \"github.com/aws/aws-sdk-go-v2/service/sqs\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sqs/types\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\tcnf                  *config.Config\n\treceiveMessageOutput *awssqs.ReceiveMessageOutput\n)\n\nfunc init() {\n\tcnf = sqs.NewTestConfig()\n\treceiveMessageOutput = sqs.ReceiveMessageOutput\n}\n\nfunc TestNewAWSSQSBroker(t *testing.T) {\n\tt.Parallel()\n\n\ttestBroker := sqs.NewTestBroker(cnf)\n\n\tbroker, err := sqs.New(cnf)\n\trequire.NoError(t, err)\n\n\tassert.IsType(t, testBroker, broker)\n}\n\nfunc TestPrivateFunc_continueReceivingMessages(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker(cnf)\n\terrorBroker := sqs.NewTestErrorBroker()\n\n\tqURL := broker.DefaultQueueURLForTest()\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput)\n\tfirstStep := make(chan int)\n\tnextStep := make(chan int)\n\tgo func() {\n\t\tstopReceivingChan := broker.GetStopReceivingChanForTest()\n\t\tfirstStep <- 1\n\t\tstopReceivingChan <- 1\n\t}()\n\n\tvar (\n\t\twhetherContinue bool\n\t\terr             error\n\t)\n\t<-firstStep\n\t// Test the case that a signal was received from stopReceivingChan\n\tgo func() {\n\t\twhetherContinue, err = broker.ContinueReceivingMessagesForTest(qURL, deliveries)\n\t\tnextStep <- 1\n\t}()\n\t<-nextStep\n\tassert.False(t, whetherContinue)\n\tassert.Nil(t, err)\n\n\t// Test the default condition\n\twhetherContinue, err = broker.ContinueReceivingMessagesForTest(qURL, deliveries)\n\tassert.True(t, whetherContinue)\n\tassert.Nil(t, err)\n\n\t// Test the error\n\twhetherContinue, err = errorBroker.ContinueReceivingMessagesForTest(qURL, deliveries)\n\tassert.True(t, whetherContinue)\n\tassert.NotNil(t, err)\n\n\t// Test when there is no message\n\toutputCopy := *receiveMessageOutput\n\treceiveMessageOutput.Messages = []types.Message{}\n\twhetherContinue, err = broker.ContinueReceivingMessagesForTest(qURL, deliveries)\n\tassert.True(t, whetherContinue)\n\tassert.Nil(t, err)\n\t// recover original value\n\t*receiveMessageOutput = outputCopy\n}\n\nfunc TestPrivateFunc_consume(t *testing.T) {\n\n\tserver1 := machinery.NewServer(cnf, eager.New(), eagerbck.New(), eagerlock.New())\n\tpool := make(chan struct{})\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput)\n\toutputCopy := *receiveMessageOutput\n\toutputCopy.Messages = []types.Message{}\n\tgo func() { deliveries <- &outputCopy }()\n\n\tbroker := sqs.NewTestBroker(cnf)\n\n\t// an infinite loop will be executed only when there is no error\n\terr := broker.ConsumeForTest(deliveries, 0, wk, pool)\n\tassert.NotNil(t, err)\n}\n\nfunc TestPrivateFunc_consumeOne(t *testing.T) {\n\tserver1 := machinery.NewServer(cnf, eager.New(), eagerbck.New(), eagerlock.New())\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\tbroker := sqs.NewTestBroker(cnf)\n\n\terr := broker.ConsumeOneForTest(receiveMessageOutput, wk)\n\tassert.Error(t, err)\n\n\toutputCopy := *receiveMessageOutput\n\toutputCopy.Messages = []types.Message{}\n\terr = broker.ConsumeOneForTest(&outputCopy, wk)\n\tassert.Error(t, err)\n\n\toutputCopy.Messages = []types.Message{\n\t\t{\n\t\t\tBody: aws.String(\"foo message\"),\n\t\t},\n\t}\n\terr = broker.ConsumeOneForTest(&outputCopy, wk)\n\tassert.Error(t, err)\n}\n\nfunc TestPrivateFunc_consumeOneWithVisibilityHeartBeat(t *testing.T) {\n\n\tcfg := sqs.NewTestConfig()\n\tcfg.SQS.VisibilityHeartBeat = true\n\tcfg.SQS.VisibilityTimeout = aws.Int(1) // seconds\n\n\tmockClient := new(MockSQSAPI)\n\n\tcfg.SQS.Client = mockClient\n\n\tbroker := sqs.NewTestBroker(cfg)\n\n\tserver1 := machinery.NewServer(cfg, broker, eagerbck.New(), eagerlock.New())\n\n\t// Long-running task by two times the visibility timeout.\n\terr := server1.RegisterTask(\"test-task\", func(ctx context.Context) error {\n\t\ttime.Sleep(time.Duration(*cfg.SQS.VisibilityTimeout) * 2 * time.Second)\n\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\n\treceiveMessageOutput.Messages = []types.Message{\n\t\t{\n\t\t\tBody: aws.String(`{\"Name\": \"test-task\"}`),\n\t\t},\n\t}\n\n\tmockClient.On(\"ChangeMessageVisibility\", mock.AnythingOfType(\"*sqs.ChangeMessageVisibilityInput\")).Return(&awssqs.ChangeMessageVisibilityOutput{}, nil)\n\tmockClient.On(\"DeleteMessage\", mock.AnythingOfType(\"*sqs.DeleteMessageInput\")).Return(&awssqs.DeleteMessageOutput{}, nil)\n\n\terr = broker.ConsumeOneForTest(receiveMessageOutput, wk)\n\tassert.NoError(t, err)\n\n\ttime.Sleep(time.Duration(*cfg.SQS.VisibilityTimeout) * time.Second)\n\n\t// Assert that ChangeMessageVisibility was called.\n\tmockClient.AssertNumberOfCalls(t, \"ChangeMessageVisibility\", 4)\n}\n\nfunc TestPrivateFunc_initializePool(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker(cnf)\n\n\tconcurrency := 9\n\tpool := make(chan struct{}, concurrency)\n\tbroker.InitializePoolForTest(pool, concurrency)\n\tassert.Len(t, pool, concurrency)\n}\n\nfunc TestPrivateFunc_startConsuming(t *testing.T) {\n\n\tserver1 := machinery.NewServer(cnf, eager.New(), eagerbck.New(), eagerlock.New())\n\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\tbroker := sqs.NewTestBroker(cnf)\n\n\tretryFunc := broker.GetRetryFuncForTest()\n\tstopChan := broker.GetStopChanForTest()\n\tretryStopChan := broker.GetRetryStopChanForTest()\n\tassert.Nil(t, retryFunc)\n\n\tbroker.StartConsumingForTest(\"fooTag\", 1, wk)\n\tassert.IsType(t, retryFunc, retry.Closure())\n\tassert.Equal(t, len(stopChan), 0)\n\tassert.Equal(t, len(retryStopChan), 0)\n}\n\nfunc TestPrivateFuncDefaultQueueURL(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker(cnf)\n\n\tqURL := broker.DefaultQueueURLForTest()\n\n\tassert.EqualValues(t, *qURL, \"https://sqs.foo.amazonaws.com.cn/test_queue\")\n}\n\nfunc TestPrivateFunc_stopReceiving(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker(cnf)\n\n\tgo broker.StopReceivingForTest()\n\n\tstopReceivingChan := broker.GetStopReceivingChanForTest()\n\tassert.NotNil(t, <-stopReceivingChan)\n}\n\nfunc TestPrivateFunc_receiveMessage(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker(cnf)\n\n\tqURL := broker.DefaultQueueURLForTest()\n\toutput, err := broker.ReceiveMessageForTest(qURL)\n\tassert.Nil(t, err)\n\tassert.Equal(t, receiveMessageOutput, output)\n}\n\nfunc TestPrivateFunc_consumeDeliveries(t *testing.T) {\n\n\tconcurrency := 0\n\tpool := make(chan struct{}, concurrency)\n\terrorsChan := make(chan error)\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput)\n\tserver1 := machinery.NewServer(cnf, eager.New(), eagerbck.New(), eagerlock.New())\n\n\twk := server1.NewWorker(\"sms_worker\", 0)\n\tbroker := sqs.NewTestBroker(cnf)\n\n\tgo func() { deliveries <- receiveMessageOutput }()\n\twhetherContinue, err := broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\tassert.True(t, whetherContinue)\n\tassert.Nil(t, err)\n\n\tgo func() { errorsChan <- errors.New(\"foo error\") }()\n\twhetherContinue, err = broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\tassert.False(t, whetherContinue)\n\tassert.NotNil(t, err)\n\n\tgo func() { broker.GetStopChanForTest() <- 1 }()\n\twhetherContinue, err = broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\tassert.False(t, whetherContinue)\n\tassert.Nil(t, err)\n\n\toutputCopy := *receiveMessageOutput\n\toutputCopy.Messages = []types.Message{}\n\tgo func() { deliveries <- &outputCopy }()\n\twhetherContinue, err = broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\te := <-errorsChan\n\tassert.True(t, whetherContinue)\n\tassert.NotNil(t, e)\n\tassert.Nil(t, err)\n\n\t// using a wait group and a channel to fix the racing problem\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tnextStep := make(chan bool, 1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t// nextStep <- true runs after defer wg.Done(), to make sure the next go routine runs after this go routine\n\t\tnextStep <- true\n\t\tdeliveries <- receiveMessageOutput\n\t}()\n\tif <-nextStep {\n\t\t// <-pool will block the routine in the following steps, so pool <- struct{}{} will be executed for sure\n\t\tgo func() { wg.Wait(); pool <- struct{}{} }()\n\t}\n\twhetherContinue, err = broker.ConsumeDeliveriesForTest(deliveries, concurrency, wk, pool, errorsChan)\n\t// the pool shouldn't be consumed\n\tp := <-pool\n\tassert.True(t, whetherContinue)\n\tassert.NotNil(t, p)\n\tassert.Nil(t, err)\n}\n\nfunc TestPrivateFunc_deleteOne(t *testing.T) {\n\n\tbroker := sqs.NewTestBroker(cnf)\n\terrorBroker := sqs.NewTestErrorBroker()\n\n\terr := broker.DeleteOneForTest(receiveMessageOutput)\n\tassert.Nil(t, err)\n\n\terr = errorBroker.DeleteOneForTest(receiveMessageOutput)\n\tassert.NotNil(t, err)\n}\n\nfunc Test_CustomQueueName(t *testing.T) {\n\n\tserver1 := machinery.NewServer(cnf, eager.New(), eagerbck.New(), eagerlock.New())\n\n\tbroker := sqs.NewTestBroker(cnf)\n\n\twk := server1.NewWorker(\"test-worker\", 0)\n\tqURL := broker.GetQueueURLForTest(wk)\n\tassert.Equal(t, qURL, broker.DefaultQueueURLForTest(), \"\")\n\n\twk2 := server1.NewCustomQueueWorker(\"test-worker\", 0, \"my-custom-queue\")\n\tqURL2 := broker.GetQueueURLForTest(wk2)\n\tassert.Equal(t, qURL2, broker.GetCustomQueueURL(\"my-custom-queue\"), \"\")\n}\n\nfunc TestPrivateFunc_consumeWithConcurrency(t *testing.T) {\n\n\tmsg := `{\n\t\t\t\"UUID\": \"uuid-dummy-task\",\n\t\t\t\"Name\": \"test-task\",\n\t\t\t\"RoutingKey\": \"dummy-routing\"\n\t\t}\n\t\t`\n\n\ttestResp := \"47f8b355-5115-4b45-b33a-439016400411\"\n\toutput := make(chan string) // The output channel\n\n\tcnf.ResultBackend = \"eager\"\n\tserver1 := machinery.NewServer(cnf, eager.New(), eagerbck.New(), eagerlock.New())\n\n\terr := server1.RegisterTask(\"test-task\", func(ctx context.Context) error {\n\t\toutput <- testResp\n\n\t\treturn nil\n\t})\n\n\tbroker := sqs.NewTestBroker(cnf)\n\n\tbroker.SetRegisteredTaskNames([]string{\"test-task\"})\n\tassert.NoError(t, err)\n\tpool := make(chan struct{}, 1)\n\tpool <- struct{}{}\n\twk := server1.NewWorker(\"sms_worker\", 1)\n\tdeliveries := make(chan *awssqs.ReceiveMessageOutput)\n\toutputCopy := *receiveMessageOutput\n\toutputCopy.Messages = []types.Message{\n\t\t{\n\t\t\tMessageId: aws.String(\"test-sqs-msg1\"),\n\t\t\tBody:      aws.String(msg),\n\t\t},\n\t}\n\n\tgo func() {\n\t\tdeliveries <- &outputCopy\n\n\t}()\n\n\tgo func() {\n\t\terr = broker.ConsumeForTest(deliveries, 1, wk, pool)\n\t}()\n\n\tselect {\n\tcase resp := <-output:\n\t\tassert.Equal(t, testResp, resp)\n\n\tcase <-time.After(10 * time.Second):\n\t\t// call timed out\n\t\tt.Fatal(\"task not processed in 10 seconds\")\n\t}\n}\n\n// MockSQSAPI is a mock implementation of the sqsiface.SQSAPI interface\ntype MockSQSAPI struct {\n\tmock.Mock\n\n\tsqsiface.API\n}\n\nfunc (m *MockSQSAPI) ReceiveMessage(ctx context.Context, input *awssqs.ReceiveMessageInput, opts ...func(*awssqs.Options)) (*awssqs.ReceiveMessageOutput, error) {\n\targs := m.Called(input)\n\treturn args.Get(0).(*awssqs.ReceiveMessageOutput), args.Error(1)\n}\n\nfunc (m *MockSQSAPI) DeleteMessage(ctx context.Context, input *awssqs.DeleteMessageInput, opts ...func(*awssqs.Options)) (*awssqs.DeleteMessageOutput, error) {\n\targs := m.Called(input)\n\treturn args.Get(0).(*awssqs.DeleteMessageOutput), args.Error(1)\n}\n\nfunc (m *MockSQSAPI) ChangeMessageVisibility(ctx context.Context, input *awssqs.ChangeMessageVisibilityInput, opts ...func(*awssqs.Options)) (*awssqs.ChangeMessageVisibilityOutput, error) {\n\targs := m.Called(input)\n\treturn args.Get(0).(*awssqs.ChangeMessageVisibilityOutput), args.Error(1)\n}\n"
  },
  {
    "path": "v2/common/amqp.go",
    "content": "package common\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"strings\"\n\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\n// AMQPConnector ...\ntype AMQPConnector struct{}\n\n// Connect opens a connection to RabbitMQ, declares an exchange, opens a channel,\n// declares and binds the queue and enables publish notifications\nfunc (ac *AMQPConnector) Connect(urls string, urlSeparator string, tlsConfig *tls.Config, exchange, exchangeType, queueName string, queueDurable, queueDelete bool, queueBindingKey string, exchangeDeclareArgs, queueDeclareArgs, queueBindingArgs amqp.Table) (*amqp.Connection, *amqp.Channel, amqp.Queue, <-chan amqp.Confirmation, <-chan *amqp.Error, error) {\n\turlsList := []string{urls}\n\tif urlSeparator != \"\" {\n\t\turlsList = strings.Split(urls, urlSeparator)\n\t}\n\n\tvar conn *amqp.Connection\n\tvar channel *amqp.Channel\n\tvar err error\n\n\tfor _, url := range urlsList {\n\t\t// Connect to server\n\t\tconn, channel, err = ac.Open(url, tlsConfig)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn nil, nil, amqp.Queue{}, nil, nil, err\n\t}\n\n\tif exchange != \"\" {\n\t\t// Declare an exchange\n\t\tif err = channel.ExchangeDeclare(\n\t\t\texchange,            // name of the exchange\n\t\t\texchangeType,        // type\n\t\t\ttrue,                // durable\n\t\t\tfalse,               // delete when complete\n\t\t\tfalse,               // internal\n\t\t\tfalse,               // noWait\n\t\t\texchangeDeclareArgs, // arguments\n\t\t); err != nil {\n\t\t\treturn conn, channel, amqp.Queue{}, nil, nil, fmt.Errorf(\"Exchange declare error: %s\", err)\n\t\t}\n\t}\n\n\tvar queue amqp.Queue\n\tif queueName != \"\" {\n\t\t// Declare a queue\n\t\tqueue, err = channel.QueueDeclare(\n\t\t\tqueueName,        // name\n\t\t\tqueueDurable,     // durable\n\t\t\tqueueDelete,      // delete when unused\n\t\t\tfalse,            // exclusive\n\t\t\tfalse,            // no-wait\n\t\t\tqueueDeclareArgs, // arguments\n\t\t)\n\t\tif err != nil {\n\t\t\treturn conn, channel, amqp.Queue{}, nil, nil, fmt.Errorf(\"Queue declare error: %s\", err)\n\t\t}\n\n\t\t// Bind the queue\n\t\tif err = channel.QueueBind(\n\t\t\tqueue.Name,       // name of the queue\n\t\t\tqueueBindingKey,  // binding key\n\t\t\texchange,         // source exchange\n\t\t\tfalse,            // noWait\n\t\t\tqueueBindingArgs, // arguments\n\t\t); err != nil {\n\t\t\treturn conn, channel, queue, nil, nil, fmt.Errorf(\"Queue bind error: %s\", err)\n\t\t}\n\t}\n\n\t// Enable publish confirmations\n\tif err = channel.Confirm(false); err != nil {\n\t\treturn conn, channel, queue, nil, nil, fmt.Errorf(\"Channel could not be put into confirm mode: %s\", err)\n\t}\n\n\treturn conn, channel, queue, channel.NotifyPublish(make(chan amqp.Confirmation, 1)), conn.NotifyClose(make(chan *amqp.Error, 1)), nil\n}\n\n// DeleteQueue deletes a queue by name\nfunc (ac *AMQPConnector) DeleteQueue(channel *amqp.Channel, queueName string) error {\n\t// First return value is number of messages removed\n\t_, err := channel.QueueDelete(\n\t\tqueueName, // name\n\t\tfalse,     // ifUnused\n\t\tfalse,     // ifEmpty\n\t\tfalse,     // noWait\n\t)\n\n\treturn err\n}\n\n// InspectQueue provides information about a specific queue\nfunc (*AMQPConnector) InspectQueue(channel *amqp.Channel, queueName string) (*amqp.Queue, error) {\n\tqueueState, err := channel.QueueInspect(queueName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Queue inspect error: %s\", err)\n\t}\n\n\treturn &queueState, nil\n}\n\n// Open new RabbitMQ connection\nfunc (ac *AMQPConnector) Open(url string, tlsConfig *tls.Config) (*amqp.Connection, *amqp.Channel, error) {\n\t// Connect\n\t// From amqp docs: DialTLS will use the provided tls.Config when it encounters an amqps:// scheme\n\t// and will dial a plain connection when it encounters an amqp:// scheme.\n\tconn, err := amqp.DialTLS(url, tlsConfig)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"Dial error: %s\", err)\n\t}\n\n\t// Open a channel\n\tchannel, err := conn.Channel()\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"Open channel error: %s\", err)\n\t}\n\n\treturn conn, channel, nil\n}\n\n// Close connection\nfunc (ac *AMQPConnector) Close(channel *amqp.Channel, conn *amqp.Connection) error {\n\tif channel != nil {\n\t\tif err := channel.Close(); err != nil {\n\t\t\treturn fmt.Errorf(\"Close channel error: %s\", err)\n\t\t}\n\t}\n\n\tif conn != nil {\n\t\tif err := conn.Close(); err != nil {\n\t\t\treturn fmt.Errorf(\"Close connection error: %s\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "v2/common/backend.go",
    "content": "package common\n\nimport (\n\t\"github.com/RichardKnop/machinery/v2/config\"\n)\n\n// Backend represents a base backend structure\ntype Backend struct {\n\tcnf *config.Config\n}\n\n// NewBackend creates new Backend instance\nfunc NewBackend(cnf *config.Config) Backend {\n\treturn Backend{cnf: cnf}\n}\n\n// GetConfig returns config\nfunc (b *Backend) GetConfig() *config.Config {\n\treturn b.cnf\n}\n\n// IsAMQP ...\nfunc (b *Backend) IsAMQP() bool {\n\treturn false\n}\n"
  },
  {
    "path": "v2/common/broker.go",
    "content": "package common\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/retry\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\ntype registeredTaskNames struct {\n\tsync.RWMutex\n\titems []string\n}\n\n// Broker represents a base broker structure\ntype Broker struct {\n\tcnf                 *config.Config\n\tregisteredTaskNames registeredTaskNames\n\tretry               bool\n\tretryFunc           func(chan int)\n\tretryStopChan       chan int\n\tstopChan            chan int\n}\n\n// NewBroker creates new Broker instance\nfunc NewBroker(cnf *config.Config) Broker {\n\treturn Broker{\n\t\tcnf:           cnf,\n\t\tretry:         true,\n\t\tstopChan:      make(chan int),\n\t\tretryStopChan: make(chan int),\n\t}\n}\n\n// GetConfig returns config\nfunc (b *Broker) GetConfig() *config.Config {\n\treturn b.cnf\n}\n\n// GetRetry ...\nfunc (b *Broker) GetRetry() bool {\n\treturn b.retry\n}\n\n// GetRetryFunc ...\nfunc (b *Broker) GetRetryFunc() func(chan int) {\n\treturn b.retryFunc\n}\n\n// GetRetryStopChan ...\nfunc (b *Broker) GetRetryStopChan() chan int {\n\treturn b.retryStopChan\n}\n\n// GetStopChan ...\nfunc (b *Broker) GetStopChan() chan int {\n\treturn b.stopChan\n}\n\n// Publish places a new message on the default queue\nfunc (b *Broker) Publish(signature *tasks.Signature) error {\n\treturn errors.New(\"Not implemented\")\n}\n\n// SetRegisteredTaskNames sets registered task names\nfunc (b *Broker) SetRegisteredTaskNames(names []string) {\n\tb.registeredTaskNames.Lock()\n\tdefer b.registeredTaskNames.Unlock()\n\tb.registeredTaskNames.items = names\n}\n\n// IsTaskRegistered returns true if the task is registered with this broker\nfunc (b *Broker) IsTaskRegistered(name string) bool {\n\tb.registeredTaskNames.RLock()\n\tdefer b.registeredTaskNames.RUnlock()\n\tfor _, registeredTaskName := range b.registeredTaskNames.items {\n\t\tif registeredTaskName == name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// GetPendingTasks returns a slice of task.Signatures waiting in the queue\nfunc (b *Broker) GetPendingTasks(queue string) ([]*tasks.Signature, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\n// GetDelayedTasks returns a slice of task.Signatures that are scheduled, but not yet in the queue\nfunc (b *Broker) GetDelayedTasks() ([]*tasks.Signature, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\n// StartConsuming is a common part of StartConsuming method\nfunc (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) {\n\tif b.retryFunc == nil {\n\t\tb.retryFunc = retry.Closure()\n\t}\n\n}\n\n// StopConsuming is a common part of StopConsuming\nfunc (b *Broker) StopConsuming() {\n\t// Do not retry from now on\n\tb.retry = false\n\t// Stop the retry closure earlier\n\tselect {\n\tcase b.retryStopChan <- 1:\n\t\tlog.WARNING.Print(\"Stopping retry closure.\")\n\tdefault:\n\t}\n\t// Notifying the stop channel stops consuming of messages\n\tclose(b.stopChan)\n\tlog.WARNING.Print(\"Stop channel\")\n}\n\n// GetRegisteredTaskNames returns registered tasks names\nfunc (b *Broker) GetRegisteredTaskNames() []string {\n\tb.registeredTaskNames.RLock()\n\tdefer b.registeredTaskNames.RUnlock()\n\titems := b.registeredTaskNames.items\n\treturn items\n}\n\n// AdjustRoutingKey makes sure the routing key is correct.\n// If the routing key is an empty string:\n// a) set it to binding key for direct exchange type\n// b) set it to default queue name\nfunc (b *Broker) AdjustRoutingKey(s *tasks.Signature) {\n\tif s.RoutingKey != \"\" {\n\t\treturn\n\t}\n\n\ts.RoutingKey = b.GetConfig().DefaultQueue\n}\n"
  },
  {
    "path": "v2/common/broker_test.go",
    "content": "package common_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n\t\"github.com/RichardKnop/machinery/v2/common\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestIsTaskRegistered(t *testing.T) {\n\tt.Parallel()\n\n\tbroker := common.NewBroker(new(config.Config))\n\tbroker.SetRegisteredTaskNames([]string{\"foo\", \"bar\"})\n\n\tassert.True(t, broker.IsTaskRegistered(\"foo\"))\n\tassert.False(t, broker.IsTaskRegistered(\"bogus\"))\n}\n\nfunc TestAdjustRoutingKey(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\ts      *tasks.Signature\n\t\tbroker common.Broker\n\t)\n\n\tt.Run(\"with routing key\", func(t *testing.T) {\n\t\ts = &tasks.Signature{RoutingKey: \"routing_key\"}\n\t\tbroker = common.NewBroker(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t})\n\t\tbroker.AdjustRoutingKey(s)\n\t\tassert.Equal(t, \"routing_key\", s.RoutingKey)\n\t})\n\n\tt.Run(\"without routing key\", func(t *testing.T) {\n\t\ts = new(tasks.Signature)\n\t\tbroker = common.NewBroker(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t})\n\t\tbroker.AdjustRoutingKey(s)\n\t\tassert.Equal(t, \"queue\", s.RoutingKey)\n\t})\n}\n\nfunc TestGetRegisteredTaskNames(t *testing.T) {\n\tt.Parallel()\n\n\tbroker := common.NewBroker(new(config.Config))\n\tfooTasks := []string{\"foo\", \"bar\", \"baz\"}\n\tbroker.SetRegisteredTaskNames(fooTasks)\n\tassert.Equal(t, fooTasks, broker.GetRegisteredTaskNames())\n}\n\nfunc TestStopConsuming(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"stop consuming\", func(t *testing.T) {\n\t\tbroker := common.NewBroker(&config.Config{\n\t\t\tDefaultQueue: \"queue\",\n\t\t})\n\t\tbroker.StartConsuming(\"\", 1, &machinery.Worker{})\n\t\tbroker.StopConsuming()\n\t\tselect {\n\t\tcase <-broker.GetStopChan():\n\t\tdefault:\n\t\t\tassert.Fail(t, \"still blocking\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "v2/common/redis.go",
    "content": "package common\n\nimport (\n\t\"crypto/tls\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n\n\t\"github.com/RichardKnop/machinery/v2/config\"\n)\n\nvar (\n\tdefaultConfig = &config.RedisConfig{\n\t\tMaxIdle:                10,\n\t\tMaxActive:              100,\n\t\tIdleTimeout:            300,\n\t\tWait:                   true,\n\t\tReadTimeout:            15,\n\t\tWriteTimeout:           15,\n\t\tConnectTimeout:         15,\n\t\tNormalTasksPollPeriod:  1000,\n\t\tDelayedTasksPollPeriod: 20,\n\t}\n)\n\n// RedisConnector ...\ntype RedisConnector struct{}\n\n// NewPool returns a new pool of Redis connections\nfunc (rc *RedisConnector) NewPool(socketPath, host, username, password string, db int, cnf *config.RedisConfig, tlsConfig *tls.Config) *redis.Pool {\n\tif cnf == nil {\n\t\tcnf = defaultConfig\n\t}\n\treturn &redis.Pool{\n\t\tMaxIdle:     cnf.MaxIdle,\n\t\tIdleTimeout: time.Duration(cnf.IdleTimeout) * time.Second,\n\t\tMaxActive:   cnf.MaxActive,\n\t\tWait:        cnf.Wait,\n\t\tDial: func() (redis.Conn, error) {\n\t\t\tc, err := rc.open(socketPath, host, username, password, db, cnf, tlsConfig)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif db != 0 {\n\t\t\t\t_, err = c.Do(\"SELECT\", db)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn c, err\n\t\t},\n\t\t// PINGs connections that have been idle more than 10 seconds\n\t\tTestOnBorrow: func(c redis.Conn, t time.Time) error {\n\t\t\tif time.Since(t) < time.Duration(10*time.Second) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t_, err := c.Do(\"PING\")\n\t\t\treturn err\n\t\t},\n\t}\n}\n\n// Open a new Redis connection\nfunc (rc *RedisConnector) open(socketPath, host, username, password string, db int, cnf *config.RedisConfig, tlsConfig *tls.Config) (redis.Conn, error) {\n\tvar opts = []redis.DialOption{\n\t\tredis.DialDatabase(db),\n\t\tredis.DialReadTimeout(time.Duration(cnf.ReadTimeout) * time.Second),\n\t\tredis.DialWriteTimeout(time.Duration(cnf.WriteTimeout) * time.Second),\n\t\tredis.DialConnectTimeout(time.Duration(cnf.ConnectTimeout) * time.Second),\n\t\tredis.DialClientName(cnf.ClientName),\n\t}\n\n\tif tlsConfig != nil {\n\t\topts = append(opts, redis.DialTLSConfig(tlsConfig), redis.DialUseTLS(true))\n\t}\n\tif username != \"\" {\n\t\topts = append(opts, redis.DialUsername(username))\n\t}\n\n\tif password != \"\" {\n\t\topts = append(opts, redis.DialPassword(password))\n\t}\n\n\tif socketPath != \"\" {\n\t\treturn redis.Dial(\"unix\", socketPath, opts...)\n\t}\n\n\treturn redis.Dial(\"tcp\", host, opts...)\n}\n"
  },
  {
    "path": "v2/config/config.go",
    "content": "package config\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"cloud.google.com/go/pubsub\"\n\tdynamodbiface \"github.com/RichardKnop/machinery/v2/backends/iface/dynamodb\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/iface/sqs\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n)\n\nconst (\n\t// DefaultResultsExpireIn is a default time used to expire task states and group metadata from the backend\n\tDefaultResultsExpireIn = 3600\n)\n\nvar (\n\t// Start with sensible default values\n\tdefaultCnf = &Config{\n\t\tBroker:          \"amqp://guest:guest@localhost:5672/\",\n\t\tDefaultQueue:    \"machinery_tasks\",\n\t\tResultBackend:   \"amqp://guest:guest@localhost:5672/\",\n\t\tResultsExpireIn: DefaultResultsExpireIn,\n\t\tAMQP: &AMQPConfig{\n\t\t\tExchange:      \"machinery_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"machinery_task\",\n\t\t\tPrefetchCount: 3,\n\t\t},\n\t\tDynamoDB: &DynamoDBConfig{\n\t\t\tTaskStatesTable: \"task_states\",\n\t\t\tGroupMetasTable: \"group_metas\",\n\t\t},\n\t\tRedis: &RedisConfig{\n\t\t\tMaxIdle:                3,\n\t\t\tIdleTimeout:            240,\n\t\t\tReadTimeout:            15,\n\t\t\tWriteTimeout:           15,\n\t\t\tConnectTimeout:         15,\n\t\t\tNormalTasksPollPeriod:  1000,\n\t\t\tDelayedTasksPollPeriod: 500,\n\t\t},\n\t\tGCPPubSub: &GCPPubSubConfig{\n\t\t\tClient: nil,\n\t\t},\n\t}\n\n\treloadDelay = time.Second * 10\n)\n\n// Config holds all configuration for our program\ntype Config struct {\n\tBroker                  string           `yaml:\"broker\" envconfig:\"BROKER\"`\n\tLock                    string           `yaml:\"lock\" envconfig:\"LOCK\"`\n\tMultipleBrokerSeparator string           `yaml:\"multiple_broker_separator\" envconfig:\"MULTIPLE_BROKEN_SEPARATOR\"`\n\tDefaultQueue            string           `yaml:\"default_queue\" envconfig:\"DEFAULT_QUEUE\"`\n\tResultBackend           string           `yaml:\"result_backend\" envconfig:\"RESULT_BACKEND\"`\n\tResultsExpireIn         int              `yaml:\"results_expire_in\" envconfig:\"RESULTS_EXPIRE_IN\"`\n\tAMQP                    *AMQPConfig      `yaml:\"amqp\"`\n\tSQS                     *SQSConfig       `yaml:\"sqs\"`\n\tRedis                   *RedisConfig     `yaml:\"redis\"`\n\tGCPPubSub               *GCPPubSubConfig `yaml:\"-\" ignored:\"true\"`\n\tMongoDB                 *MongoDBConfig   `yaml:\"-\" ignored:\"true\"`\n\tTLSConfig               *tls.Config\n\t// NoUnixSignals - when set disables signal handling in machinery\n\tNoUnixSignals bool            `yaml:\"no_unix_signals\" envconfig:\"NO_UNIX_SIGNALS\"`\n\tDynamoDB      *DynamoDBConfig `yaml:\"dynamodb\"`\n}\n\n// QueueBindingArgs arguments which are used when binding to the exchange\ntype QueueBindingArgs map[string]interface{}\n\n// QueueDeclareArgs arguments which are used when declaring a queue\ntype QueueDeclareArgs map[string]interface{}\n\n// AMQPConfig wraps RabbitMQ related configuration\ntype AMQPConfig struct {\n\tExchange         string           `yaml:\"exchange\" envconfig:\"AMQP_EXCHANGE\"`\n\tExchangeType     string           `yaml:\"exchange_type\" envconfig:\"AMQP_EXCHANGE_TYPE\"`\n\tQueueDeclareArgs QueueDeclareArgs `yaml:\"queue_declare_args\" envconfig:\"AMQP_QUEUE_DECLARE_ARGS\"`\n\tQueueBindingArgs QueueBindingArgs `yaml:\"queue_binding_args\" envconfig:\"AMQP_QUEUE_BINDING_ARGS\"`\n\tBindingKey       string           `yaml:\"binding_key\" envconfig:\"AMQP_BINDING_KEY\"`\n\tPrefetchCount    int              `yaml:\"prefetch_count\" envconfig:\"AMQP_PREFETCH_COUNT\"`\n\tAutoDelete       bool             `yaml:\"auto_delete\" envconfig:\"AMQP_AUTO_DELETE\"`\n\tDelayedQueue     string           `yaml:\"delayed_queue\" envconfig:\"AMQP_DELAYED_QUEUE\"`\n}\n\n// DynamoDBConfig wraps DynamoDB related configuration\ntype DynamoDBConfig struct {\n\tClient          dynamodbiface.API\n\tTaskStatesTable string `yaml:\"task_states_table\" envconfig:\"TASK_STATES_TABLE\"`\n\tGroupMetasTable string `yaml:\"group_metas_table\" envconfig:\"GROUP_METAS_TABLE\"`\n}\n\n// SQSConfig wraps SQS related configuration\ntype SQSConfig struct {\n\tClient          sqs.API\n\tWaitTimeSeconds int `yaml:\"receive_wait_time_seconds\" envconfig:\"SQS_WAIT_TIME_SECONDS\"`\n\t// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html\n\t// visibility timeout should default to nil to use the overall visibility timeout for the queue\n\tVisibilityTimeout *int `yaml:\"receive_visibility_timeout\" envconfig:\"SQS_VISIBILITY_TIMEOUT\"`\n\t// https://docs.aws.amazon.com/es_es/AWSSimpleQueueService/latest/SQSDeveloperGuide/best-practices-processing-messages-timely-manner.html\n\t// visibility heartbeat should default to true to ensure that the visibility timeout is extended while the task is being processed.\n\tVisibilityHeartBeat bool `yaml:\"visibility_hearth_beat\" envconfig:\"SQS_VISIBILITY_HEARTBEAT\"`\n}\n\n// RedisConfig ...\ntype RedisConfig struct {\n\t// Maximum number of idle connections in the pool.\n\t// Default: 10\n\tMaxIdle int `yaml:\"max_idle\" envconfig:\"REDIS_MAX_IDLE\"`\n\n\t// Maximum number of connections allocated by the pool at a given time.\n\t// When zero, there is no limit on the number of connections in the pool.\n\t// Default: 100\n\tMaxActive int `yaml:\"max_active\" envconfig:\"REDIS_MAX_ACTIVE\"`\n\n\t// Close connections after remaining idle for this duration in seconds. If the value\n\t// is zero, then idle connections are not closed. Applications should set\n\t// the timeout to a value less than the server's timeout.\n\t// Default: 300\n\tIdleTimeout int `yaml:\"max_idle_timeout\" envconfig:\"REDIS_IDLE_TIMEOUT\"`\n\n\t// If Wait is true and the pool is at the MaxActive limit, then Get() waits\n\t// for a connection to be returned to the pool before returning.\n\t// Default: true\n\tWait bool `yaml:\"wait\" envconfig:\"REDIS_WAIT\"`\n\n\t// ReadTimeout specifies the timeout in seconds for reading a single command reply.\n\t// Default: 15\n\tReadTimeout int `yaml:\"read_timeout\" envconfig:\"REDIS_READ_TIMEOUT\"`\n\n\t// WriteTimeout specifies the timeout in seconds for writing a single command.\n\t// Default: 15\n\tWriteTimeout int `yaml:\"write_timeout\" envconfig:\"REDIS_WRITE_TIMEOUT\"`\n\n\t// ConnectTimeout specifies the timeout in seconds for connecting to the Redis server when\n\t// no DialNetDial option is specified.\n\t// Default: 15\n\tConnectTimeout int `yaml:\"connect_timeout\" envconfig:\"REDIS_CONNECT_TIMEOUT\"`\n\n\t// NormalTasksPollPeriod specifies the period in milliseconds when polling redis for normal tasks\n\t// Default: 1000\n\tNormalTasksPollPeriod int `yaml:\"normal_tasks_poll_period\" envconfig:\"REDIS_NORMAL_TASKS_POLL_PERIOD\"`\n\n\t// DelayedTasksPollPeriod specifies the period in milliseconds when polling redis for delayed tasks\n\t// Default: 20\n\tDelayedTasksPollPeriod int    `yaml:\"delayed_tasks_poll_period\" envconfig:\"REDIS_DELAYED_TASKS_POLL_PERIOD\"`\n\tDelayedTasksKey        string `yaml:\"delayed_tasks_key\" envconfig:\"REDIS_DELAYED_TASKS_KEY\"`\n\n\t// ClientName specifies the redis client name to be set when connecting to the Redis server\n\tClientName string `yaml:\"client_name\" envconfig:\"REDIS_CLIENT_NAME\"`\n\n\t// MasterName specifies a redis master name in order to configure a sentinel-backed redis FailoverClient\n\tMasterName string `yaml:\"master_name\" envconfig:\"REDIS_MASTER_NAME\"`\n\n\t// ClusterEnabled specifies whether cluster mode is enabled, regardless the number of addresses.\n\t// This helps create ClusterClient for Redis servers that enabled cluster mode with 1 node, or using AWS configuration endpoint\n\tClusterEnabled bool `yaml:\"cluster_enabled\" envconfig:\"REDIS_CLUSTER_ENABLED\"`\n\n\t// SentinelPassword specifies the password to be used when connecting to a Redis server via Sentinel\n\tSentinelPassword string `yaml:\"sentinel_password\" envconfig:\"REDIS_SENTINEL_PASSWORD\"`\n}\n\n// GCPPubSubConfig wraps GCP PubSub related configuration\ntype GCPPubSubConfig struct {\n\tClient       *pubsub.Client\n\tMaxExtension time.Duration\n}\n\n// MongoDBConfig ...\ntype MongoDBConfig struct {\n\tClient   *mongo.Client\n\tDatabase string\n}\n\n// Decode from yaml to map (any field whose type or pointer-to-type implements\n// envconfig.Decoder can control its own deserialization)\nfunc (args *QueueBindingArgs) Decode(value string) error {\n\tpairs := strings.Split(value, \",\")\n\tmp := make(map[string]interface{}, len(pairs))\n\tfor _, pair := range pairs {\n\t\tkvpair := strings.Split(pair, \":\")\n\t\tif len(kvpair) != 2 {\n\t\t\treturn fmt.Errorf(\"invalid map item: %q\", pair)\n\t\t}\n\t\tmp[kvpair[0]] = kvpair[1]\n\t}\n\t*args = QueueBindingArgs(mp)\n\treturn nil\n}\n"
  },
  {
    "path": "v2/config/env.go",
    "content": "package config\n\nimport (\n\t\"github.com/kelseyhightower/envconfig\"\n\n\t\"github.com/RichardKnop/machinery/v2/log\"\n)\n\n// NewFromEnvironment creates a config object from environment variables\nfunc NewFromEnvironment() (*Config, error) {\n\tcnf, err := fromEnvironment()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.INFO.Print(\"Successfully loaded config from the environment\")\n\n\treturn cnf, nil\n}\n\nfunc fromEnvironment() (*Config, error) {\n\tloadedCnf, cnf := new(Config), new(Config)\n\t*cnf = *defaultCnf\n\n\tif err := envconfig.Process(\"\", cnf); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := envconfig.Process(\"\", loadedCnf); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif loadedCnf.AMQP == nil {\n\t\tcnf.AMQP = nil\n\t}\n\n\treturn cnf, nil\n}\n"
  },
  {
    "path": "v2/config/env_test.go",
    "content": "package config_test\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewFromEnvironment(t *testing.T) {\n\tt.Parallel()\n\n\tfile, err := os.Open(\"test.env\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treader := bufio.NewReader(file)\n\tscanner := bufio.NewScanner(reader)\n\tscanner.Split(bufio.ScanLines)\n\tfor scanner.Scan() {\n\t\tparts := strings.Split(scanner.Text(), \"=\")\n\t\tif len(parts) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tos.Setenv(parts[0], parts[1])\n\t}\n\n\tcnf, err := config.NewFromEnvironment()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, \"broker\", cnf.Broker)\n\tassert.Equal(t, \"default_queue\", cnf.DefaultQueue)\n\tassert.Equal(t, \"result_backend\", cnf.ResultBackend)\n\tassert.Equal(t, 123456, cnf.ResultsExpireIn)\n\tassert.Equal(t, \"exchange\", cnf.AMQP.Exchange)\n\tassert.Equal(t, \"exchange_type\", cnf.AMQP.ExchangeType)\n\tassert.Equal(t, \"binding_key\", cnf.AMQP.BindingKey)\n\tassert.Equal(t, \"any\", cnf.AMQP.QueueBindingArgs[\"x-match\"])\n\tassert.Equal(t, \"png\", cnf.AMQP.QueueBindingArgs[\"image-type\"])\n\tassert.Equal(t, 123, cnf.AMQP.PrefetchCount)\n}\n"
  },
  {
    "path": "v2/config/file.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"gopkg.in/yaml.v2\"\n)\n\n// NewFromYaml creates a config object from YAML file\nfunc NewFromYaml(cnfPath string, keepReloading bool) (*Config, error) {\n\tcnf, err := fromFile(cnfPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.INFO.Printf(\"Successfully loaded config from file %s\", cnfPath)\n\n\tif keepReloading {\n\t\t// Open a goroutine to watch remote changes forever\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\t// Delay after each request\n\t\t\t\ttime.Sleep(reloadDelay)\n\n\t\t\t\t// Attempt to reload the config\n\t\t\t\tnewCnf, newErr := fromFile(cnfPath)\n\t\t\t\tif newErr != nil {\n\t\t\t\t\tlog.WARNING.Printf(\"Failed to reload config from file %s: %v\", cnfPath, newErr)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t*cnf = *newCnf\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn cnf, nil\n}\n\n// ReadFromFile reads data from a file\nfunc ReadFromFile(cnfPath string) ([]byte, error) {\n\tfile, err := os.Open(cnfPath)\n\n\t// Config file not found\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Open file error: %s\", err)\n\t}\n\tdefer file.Close()\n\n\t// Config file found, let's try to read it\n\tdata := make([]byte, 1000)\n\tcount, err := file.Read(data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Read from file error: %s\", err)\n\t}\n\n\treturn data[:count], nil\n}\n\nfunc fromFile(cnfPath string) (*Config, error) {\n\tloadedCnf, cnf := new(Config), new(Config)\n\t*cnf = *defaultCnf\n\n\tdata, err := ReadFromFile(cnfPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := yaml.Unmarshal(data, cnf); err != nil {\n\t\treturn nil, fmt.Errorf(\"Unmarshal YAML error: %s\", err)\n\t}\n\tif err := yaml.Unmarshal(data, loadedCnf); err != nil {\n\t\treturn nil, fmt.Errorf(\"Unmarshal YAML error: %s\", err)\n\t}\n\tif loadedCnf.AMQP == nil {\n\t\tcnf.AMQP = nil\n\t}\n\n\treturn cnf, nil\n}\n"
  },
  {
    "path": "v2/config/file_test.go",
    "content": "package config_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar configYAMLData = `---\nbroker: broker\ndefault_queue: default_queue\nresult_backend: result_backend\nresults_expire_in: 123456\namqp:\n  binding_key: binding_key\n  exchange: exchange\n  exchange_type: exchange_type\n  prefetch_count: 123\n  queue_declare_args:\n    x-max-priority: 10\n  queue_binding_args:\n    image-type: png\n    x-match: any\nsqs:\n  receive_wait_time_seconds: 123\n  receive_visibility_timeout: 456\nredis:\n  max_idle: 12\n  max_active: 123\n  max_idle_timeout: 456\n  wait: false\n  read_timeout: 17\n  write_timeout: 19\n  connect_timeout: 21\n  normal_tasks_poll_period: 1001\n  delayed_tasks_poll_period: 23\n  delayed_tasks_key: delayed_tasks_key\n  master_name: master_name\nno_unix_signals: true\ndynamodb:\n  task_states_table: task_states_table\n  group_metas_table: group_metas_table\n`\n\nfunc TestReadFromFile(t *testing.T) {\n\tt.Parallel()\n\n\tdata, err := config.ReadFromFile(\"testconfig.yml\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, configYAMLData, string(data))\n}\n\nfunc TestNewFromYaml(t *testing.T) {\n\tt.Parallel()\n\n\tcnf, err := config.NewFromYaml(\"testconfig.yml\", false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, \"broker\", cnf.Broker)\n\tassert.Equal(t, \"default_queue\", cnf.DefaultQueue)\n\tassert.Equal(t, \"result_backend\", cnf.ResultBackend)\n\tassert.Equal(t, 123456, cnf.ResultsExpireIn)\n\n\tassert.Equal(t, \"exchange\", cnf.AMQP.Exchange)\n\tassert.Equal(t, \"exchange_type\", cnf.AMQP.ExchangeType)\n\tassert.Equal(t, \"binding_key\", cnf.AMQP.BindingKey)\n\tassert.Equal(t, 10, cnf.AMQP.QueueDeclareArgs[\"x-max-priority\"])\n\tassert.Equal(t, \"any\", cnf.AMQP.QueueBindingArgs[\"x-match\"])\n\tassert.Equal(t, \"png\", cnf.AMQP.QueueBindingArgs[\"image-type\"])\n\tassert.Equal(t, 123, cnf.AMQP.PrefetchCount)\n\n\tassert.Equal(t, 123, cnf.SQS.WaitTimeSeconds)\n\tassert.Equal(t, 456, *cnf.SQS.VisibilityTimeout)\n\n\tassert.Equal(t, 12, cnf.Redis.MaxIdle)\n\tassert.Equal(t, 123, cnf.Redis.MaxActive)\n\tassert.Equal(t, 456, cnf.Redis.IdleTimeout)\n\tassert.Equal(t, false, cnf.Redis.Wait)\n\tassert.Equal(t, 17, cnf.Redis.ReadTimeout)\n\tassert.Equal(t, 19, cnf.Redis.WriteTimeout)\n\tassert.Equal(t, 21, cnf.Redis.ConnectTimeout)\n\tassert.Equal(t, 1001, cnf.Redis.NormalTasksPollPeriod)\n\tassert.Equal(t, 23, cnf.Redis.DelayedTasksPollPeriod)\n\tassert.Equal(t, \"delayed_tasks_key\", cnf.Redis.DelayedTasksKey)\n\tassert.Equal(t, \"master_name\", cnf.Redis.MasterName)\n\n\tassert.Equal(t, true, cnf.NoUnixSignals)\n\n\tassert.Equal(t, \"task_states_table\", cnf.DynamoDB.TaskStatesTable)\n\tassert.Equal(t, \"group_metas_table\", cnf.DynamoDB.GroupMetasTable)\n}\n"
  },
  {
    "path": "v2/config/test.env",
    "content": "BROKER=broker\nDEFAULT_QUEUE=default_queue\nRESULT_BACKEND=result_backend\nRESULTS_EXPIRE_IN=123456\nAMQP_BINDING_KEY=binding_key\nAMQP_EXCHANGE=exchange\nAMQP_EXCHANGE_TYPE=exchange_type\nAMQP_PREFETCH_COUNT=123\nAMQP_QUEUE_BINDING_ARGS=image-type:png,x-match:any\n"
  },
  {
    "path": "v2/config/testconfig.yml",
    "content": "---\nbroker: broker\ndefault_queue: default_queue\nresult_backend: result_backend\nresults_expire_in: 123456\namqp:\n  binding_key: binding_key\n  exchange: exchange\n  exchange_type: exchange_type\n  prefetch_count: 123\n  queue_declare_args:\n    x-max-priority: 10\n  queue_binding_args:\n    image-type: png\n    x-match: any\nsqs:\n  receive_wait_time_seconds: 123\n  receive_visibility_timeout: 456\nredis:\n  max_idle: 12\n  max_active: 123\n  max_idle_timeout: 456\n  wait: false\n  read_timeout: 17\n  write_timeout: 19\n  connect_timeout: 21\n  normal_tasks_poll_period: 1001\n  delayed_tasks_poll_period: 23\n  delayed_tasks_key: delayed_tasks_key\n  master_name: master_name\nno_unix_signals: true\ndynamodb:\n  task_states_table: task_states_table\n  group_metas_table: group_metas_table\n"
  },
  {
    "path": "v2/docker-compose.test.yml",
    "content": "version: \"2\"\n\nservices:\n  sut:\n    container_name: machinery_sut\n    image: machinery_sut:latest\n    volumes:\n      - \"./:/go/src/github.com/RichardKnop/machinery/v2\"\n    depends_on:\n      - rabbitmq\n      - redis\n    links:\n      - rabbitmq\n      - redis\n    build:\n      context: .\n      dockerfile: ./Dockerfile.test\n    environment:\n      AMQP_URLS: 'amqp://guest:guest@dummy:5672/,amqp://guest:guest@rabbitmq:5672/'\n      AMQP_URLS_SEPARATOR: ','\n      REDIS_URL: 'redis:6379'\n      SQS_URL: ${SQS_URL}\n      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}\n      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}\n      AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION}\n      AWS_REGION: 'us-west-2'\n\n  rabbitmq:\n    container_name: machinery_sut_rabbitmq\n    image: rabbitmq\n    environment:\n      - RABBITMQ_DEFAULT_USER=guest\n      - RABBITMQ_DEFAULT_PASS=guest\n    logging:\n      driver: none\n\n  redis:\n    container_name: machinery_sut_redis\n    image: redis\n    logging:\n      driver: none\n"
  },
  {
    "path": "v2/example/amqp/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/urfave/cli\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\n\tamqpbackend \"github.com/RichardKnop/machinery/v2/backends/amqp\"\n\tamqpbroker \"github.com/RichardKnop/machinery/v2/brokers/amqp\"\n\texampletasks \"github.com/RichardKnop/machinery/v2/example/tasks\"\n\t\"github.com/RichardKnop/machinery/v2/example/tracers\"\n\teagerlock \"github.com/RichardKnop/machinery/v2/locks/eager\"\n\t\"github.com/opentracing/opentracing-go\"\n\topentracinglog \"github.com/opentracing/opentracing-go/log\"\n)\n\nvar (\n\tapp *cli.App\n)\n\nfunc init() {\n\t// Initialise a CLI app\n\tapp = cli.NewApp()\n\tapp.Name = \"machinery\"\n\tapp.Usage = \"machinery worker and send example tasks with machinery send\"\n\tapp.Version = \"0.0.0\"\n}\n\nfunc main() {\n\t// Set the CLI app commands\n\tapp.Commands = []cli.Command{\n\t\t{\n\t\t\tName:  \"worker\",\n\t\t\tUsage: \"launch machinery worker\",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := worker(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:  \"send\",\n\t\t\tUsage: \"send example tasks \",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := send(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t}\n\n\t// Run the CLI app\n\t_ = app.Run(os.Args)\n}\n\nfunc startServer() (*machinery.Server, error) {\n\tcnf := &config.Config{\n\t\tBroker:          \"amqp://guest:guest@localhost:5672/\",\n\t\tDefaultQueue:    \"machinery_tasks\",\n\t\tResultBackend:   \"amqp://guest:guest@localhost:5672/\",\n\t\tResultsExpireIn: 3600,\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"machinery_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"machinery_task\",\n\t\t\tPrefetchCount: 3,\n\t\t},\n\t}\n\n\t// Create server instance\n\tbroker := amqpbroker.New(cnf)\n\tbackend := amqpbackend.New(cnf)\n\tlock := eagerlock.New()\n\tserver := machinery.NewServer(cnf, broker, backend, lock)\n\n\t// Register tasks\n\ttasksMap := map[string]interface{}{\n\t\t\"add\":               exampletasks.Add,\n\t\t\"multiply\":          exampletasks.Multiply,\n\t\t\"sum_ints\":          exampletasks.SumInts,\n\t\t\"sum_floats\":        exampletasks.SumFloats,\n\t\t\"concat\":            exampletasks.Concat,\n\t\t\"split\":             exampletasks.Split,\n\t\t\"panic_task\":        exampletasks.PanicTask,\n\t\t\"long_running_task\": exampletasks.LongRunningTask,\n\t}\n\n\treturn server, server.RegisterTasks(tasksMap)\n}\n\nfunc worker() error {\n\tconsumerTag := \"machinery_worker\"\n\n\tcleanup, err := tracers.SetupTracer(consumerTag)\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// The second argument is a consumer tag\n\t// Ideally, each worker should have a unique tag (worker1, worker2 etc)\n\tworker := server.NewWorker(consumerTag, 0)\n\n\t// Here we inject some custom code for error handling,\n\t// start and end of task hooks, useful for metrics for example.\n\terrorHandler := func(err error) {\n\t\tlog.ERROR.Println(\"I am an error handler:\", err)\n\t}\n\n\tpreTaskHandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am a start of task handler for:\", signature.Name)\n\t}\n\n\tpostTaskHandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am an end of task handler for:\", signature.Name)\n\t}\n\n\tworker.SetPostTaskHandler(postTaskHandler)\n\tworker.SetErrorHandler(errorHandler)\n\tworker.SetPreTaskHandler(preTaskHandler)\n\n\treturn worker.Launch()\n}\n\nfunc send() error {\n\tcleanup, err := tracers.SetupTracer(\"sender\")\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar (\n\t\taddTask0, addTask1, addTask2                      tasks.Signature\n\t\tmultiplyTask0, multiplyTask1                      tasks.Signature\n\t\tsumIntsTask, sumFloatsTask, concatTask, splitTask tasks.Signature\n\t\tpanicTask                                         tasks.Signature\n\t\tlongRunningTask                                   tasks.Signature\n\t)\n\n\tvar initTasks = func() {\n\t\taddTask0 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask1 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask2 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 5,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 6,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask0 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 4,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask1 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t}\n\n\t\tsumIntsTask = tasks.Signature{\n\t\t\tName: \"sum_ints\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]int64\",\n\t\t\t\t\tValue: []int64{1, 2},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsumFloatsTask = tasks.Signature{\n\t\t\tName: \"sum_floats\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]float64\",\n\t\t\t\t\tValue: []float64{1.5, 2.7},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tconcatTask = tasks.Signature{\n\t\t\tName: \"concat\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]string\",\n\t\t\t\t\tValue: []string{\"foo\", \"bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsplitTask = tasks.Signature{\n\t\t\tName: \"split\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"string\",\n\t\t\t\t\tValue: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tpanicTask = tasks.Signature{\n\t\t\tName: \"panic_task\",\n\t\t}\n\n\t\tlongRunningTask = tasks.Signature{\n\t\t\tName: \"long_running_task\",\n\t\t}\n\t}\n\n\t/*\n\t * Lets start a span representing this run of the `send` command and\n\t * set a batch id as baggage so it can travel all the way into\n\t * the worker functions.\n\t */\n\tspan, ctx := opentracing.StartSpanFromContext(context.Background(), \"send\")\n\tdefer span.Finish()\n\n\tbatchID := uuid.New().String()\n\tspan.SetBaggageItem(\"batch.id\", batchID)\n\tspan.LogFields(opentracinglog.String(\"batch.id\", batchID))\n\n\tlog.INFO.Println(\"Starting batch:\", batchID)\n\t/*\n\t * First, let's try sending a single task\n\t */\n\tinitTasks()\n\n\tlog.INFO.Println(\"Single task:\")\n\n\tasyncResult, err := server.SendTaskWithContext(ctx, &addTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err := asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"1 + 1 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Try couple of tasks with a slice argument and slice return value\n\t */\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumIntsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1, 2]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumFloatsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1.5, 2.7]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &concatTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"concat([\\\"foo\\\", \\\"bar\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &splitTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"split([\\\"foo\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Now let's explore ways of sending multiple tasks\n\t */\n\n\t// Now let's try a parallel execution\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks (parallel execution):\")\n\n\tgroup, err := tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tasyncResults, err := server.SendGroupWithContext(ctx, group, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send group: %s\", err.Error())\n\t}\n\n\tfor _, asyncResult := range asyncResults {\n\t\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t\t}\n\t\tlog.INFO.Printf(\n\t\t\t\"%v + %v = %v\\n\",\n\t\t\tasyncResult.Signature.Args[0].Value,\n\t\t\tasyncResult.Signature.Args[1].Value,\n\t\t\ttasks.HumanReadableResults(results),\n\t\t)\n\t}\n\n\t// Now let's try a group with a chord\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks with a callback (chord):\")\n\n\tgroup, err = tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tchord, err := tasks.NewChord(group, &multiplyTask1)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chord: %s\", err)\n\t}\n\n\tchordAsyncResult, err := server.SendChordWithContext(ctx, chord, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chord: %s\", err.Error())\n\t}\n\n\tresults, err = chordAsyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chord result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(1 + 1) * (2 + 2) * (5 + 6) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Now let's try chaining task results\n\tinitTasks()\n\tlog.INFO.Println(\"Chain of tasks:\")\n\n\tchain, err := tasks.NewChain(&addTask0, &addTask1, &addTask2, &multiplyTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chain: %s\", err)\n\t}\n\n\tchainAsyncResult, err := server.SendChainWithContext(ctx, chain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chain: %s\", err.Error())\n\t}\n\n\tresults, err = chainAsyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chain result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(((1 + 1) + (2 + 2)) + (5 + 6)) * 4 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Let's try a task which throws panic to make sure stack trace is not lost\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &panicTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\t_, err = asyncResult.Get(time.Millisecond * 5)\n\tif err == nil {\n\t\treturn errors.New(\"Error should not be nil if task panicked\")\n\t}\n\tlog.INFO.Printf(\"Task panicked and returned error = %v\\n\", err.Error())\n\n\t// Let's try a long running task\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &longRunningTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting long running task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"Long running task returned = %v\\n\", tasks.HumanReadableResults(results))\n\n\treturn nil\n}\n"
  },
  {
    "path": "v2/example/go-redis/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/urfave/cli\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\n\tredisbackend \"github.com/RichardKnop/machinery/v2/backends/redis\"\n\tredisbroker \"github.com/RichardKnop/machinery/v2/brokers/redis\"\n\texampletasks \"github.com/RichardKnop/machinery/v2/example/tasks\"\n\t\"github.com/RichardKnop/machinery/v2/example/tracers\"\n\teagerlock \"github.com/RichardKnop/machinery/v2/locks/eager\"\n\t\"github.com/opentracing/opentracing-go\"\n\topentracinglog \"github.com/opentracing/opentracing-go/log\"\n)\n\nvar (\n\tapp *cli.App\n)\n\nfunc init() {\n\t// Initialise a CLI app\n\tapp = cli.NewApp()\n\tapp.Name = \"machinery\"\n\tapp.Usage = \"machinery worker and send example tasks with machinery send\"\n\tapp.Version = \"0.0.0\"\n}\n\nfunc main() {\n\t// Set the CLI app commands\n\tapp.Commands = []cli.Command{\n\t\t{\n\t\t\tName:  \"worker\",\n\t\t\tUsage: \"launch machinery worker\",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := worker(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:  \"send\",\n\t\t\tUsage: \"send example tasks \",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := send(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t}\n\n\t// Run the CLI app\n\t_ = app.Run(os.Args)\n}\n\nfunc startServer() (*machinery.Server, error) {\n\tcnf := &config.Config{\n\t\tDefaultQueue:    \"machinery_tasks\",\n\t\tResultsExpireIn: 3600,\n\t\tRedis: &config.RedisConfig{\n\t\t\tMaxIdle:                3,\n\t\t\tIdleTimeout:            240,\n\t\t\tReadTimeout:            15,\n\t\t\tWriteTimeout:           15,\n\t\t\tConnectTimeout:         15,\n\t\t\tNormalTasksPollPeriod:  1000,\n\t\t\tDelayedTasksPollPeriod: 500,\n\t\t},\n\t}\n\n\t// Create server instance\n\tbroker := redisbroker.NewGR(cnf, []string{\"localhost:6379\"}, 0)\n\tbackend := redisbackend.NewGR(cnf, []string{\"localhost:6379\"}, 0)\n\tlock := eagerlock.New()\n\tserver := machinery.NewServer(cnf, broker, backend, lock)\n\n\t// Register tasks\n\ttasksMap := map[string]interface{}{\n\t\t\"add\":               exampletasks.Add,\n\t\t\"multiply\":          exampletasks.Multiply,\n\t\t\"sum_ints\":          exampletasks.SumInts,\n\t\t\"sum_floats\":        exampletasks.SumFloats,\n\t\t\"concat\":            exampletasks.Concat,\n\t\t\"split\":             exampletasks.Split,\n\t\t\"panic_task\":        exampletasks.PanicTask,\n\t\t\"long_running_task\": exampletasks.LongRunningTask,\n\t}\n\n\treturn server, server.RegisterTasks(tasksMap)\n}\n\nfunc worker() error {\n\tconsumerTag := \"machinery_worker\"\n\n\tcleanup, err := tracers.SetupTracer(consumerTag)\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// The second argument is a consumer tag\n\t// Ideally, each worker should have a unique tag (worker1, worker2 etc)\n\tworker := server.NewWorker(consumerTag, 0)\n\n\t// Here we inject some custom code for error handling,\n\t// start and end of task hooks, useful for metrics for example.\n\terrorHandler := func(err error) {\n\t\tlog.ERROR.Println(\"I am an error handler:\", err)\n\t}\n\n\tpreTaskHandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am a start of task handler for:\", signature.Name)\n\t}\n\n\tpostTaskHandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am an end of task handler for:\", signature.Name)\n\t}\n\n\tworker.SetPostTaskHandler(postTaskHandler)\n\tworker.SetErrorHandler(errorHandler)\n\tworker.SetPreTaskHandler(preTaskHandler)\n\n\treturn worker.Launch()\n}\n\nfunc send() error {\n\tcleanup, err := tracers.SetupTracer(\"sender\")\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar (\n\t\taddTask0, addTask1, addTask2                      tasks.Signature\n\t\tmultiplyTask0, multiplyTask1                      tasks.Signature\n\t\tsumIntsTask, sumFloatsTask, concatTask, splitTask tasks.Signature\n\t\tpanicTask                                         tasks.Signature\n\t\tlongRunningTask                                   tasks.Signature\n\t)\n\n\tvar initTasks = func() {\n\t\taddTask0 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask1 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask2 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 5,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 6,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask0 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 4,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask1 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t}\n\n\t\tsumIntsTask = tasks.Signature{\n\t\t\tName: \"sum_ints\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]int64\",\n\t\t\t\t\tValue: []int64{1, 2},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsumFloatsTask = tasks.Signature{\n\t\t\tName: \"sum_floats\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]float64\",\n\t\t\t\t\tValue: []float64{1.5, 2.7},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tconcatTask = tasks.Signature{\n\t\t\tName: \"concat\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]string\",\n\t\t\t\t\tValue: []string{\"foo\", \"bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsplitTask = tasks.Signature{\n\t\t\tName: \"split\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"string\",\n\t\t\t\t\tValue: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tpanicTask = tasks.Signature{\n\t\t\tName: \"panic_task\",\n\t\t}\n\n\t\tlongRunningTask = tasks.Signature{\n\t\t\tName: \"long_running_task\",\n\t\t}\n\t}\n\n\t/*\n\t * Lets start a span representing this run of the `send` command and\n\t * set a batch id as baggage so it can travel all the way into\n\t * the worker functions.\n\t */\n\tspan, ctx := opentracing.StartSpanFromContext(context.Background(), \"send\")\n\tdefer span.Finish()\n\n\tbatchID := uuid.New().String()\n\tspan.SetBaggageItem(\"batch.id\", batchID)\n\tspan.LogFields(opentracinglog.String(\"batch.id\", batchID))\n\n\tlog.INFO.Println(\"Starting batch:\", batchID)\n\t/*\n\t * First, let's try sending a single task\n\t */\n\tinitTasks()\n\n\tlog.INFO.Println(\"Single task:\")\n\n\tasyncResult, err := server.SendTaskWithContext(ctx, &addTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err := asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"1 + 1 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Try couple of tasks with a slice argument and slice return value\n\t */\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumIntsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1, 2]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumFloatsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1.5, 2.7]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &concatTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"concat([\\\"foo\\\", \\\"bar\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &splitTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"split([\\\"foo\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Now let's explore ways of sending multiple tasks\n\t */\n\n\t// Now let's try a parallel execution\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks (parallel execution):\")\n\n\tgroup, err := tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tasyncResults, err := server.SendGroupWithContext(ctx, group, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send group: %s\", err.Error())\n\t}\n\n\tfor _, asyncResult := range asyncResults {\n\t\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t\t}\n\t\tlog.INFO.Printf(\n\t\t\t\"%v + %v = %v\\n\",\n\t\t\tasyncResult.Signature.Args[0].Value,\n\t\t\tasyncResult.Signature.Args[1].Value,\n\t\t\ttasks.HumanReadableResults(results),\n\t\t)\n\t}\n\n\t// Now let's try a group with a chord\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks with a callback (chord):\")\n\n\tgroup, err = tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tchord, err := tasks.NewChord(group, &multiplyTask1)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chord: %s\", err)\n\t}\n\n\tchordAsyncResult, err := server.SendChordWithContext(ctx, chord, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chord: %s\", err.Error())\n\t}\n\n\tresults, err = chordAsyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chord result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(1 + 1) * (2 + 2) * (5 + 6) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Now let's try chaining task results\n\tinitTasks()\n\tlog.INFO.Println(\"Chain of tasks:\")\n\n\tchain, err := tasks.NewChain(&addTask0, &addTask1, &addTask2, &multiplyTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chain: %s\", err)\n\t}\n\n\tchainAsyncResult, err := server.SendChainWithContext(ctx, chain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chain: %s\", err.Error())\n\t}\n\n\tresults, err = chainAsyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chain result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(((1 + 1) + (2 + 2)) + (5 + 6)) * 4 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Let's try a task which throws panic to make sure stack trace is not lost\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &panicTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\t_, err = asyncResult.Get(time.Millisecond * 5)\n\tif err == nil {\n\t\treturn errors.New(\"Error should not be nil if task panicked\")\n\t}\n\tlog.INFO.Printf(\"Task panicked and returned error = %v\\n\", err.Error())\n\n\t// Let's try a long running task\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &longRunningTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting long running task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"Long running task returned = %v\\n\", tasks.HumanReadableResults(results))\n\n\treturn nil\n}\n"
  },
  {
    "path": "v2/example/redigo/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/urfave/cli\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\n\tredisbackend \"github.com/RichardKnop/machinery/v2/backends/redis\"\n\tredisbroker \"github.com/RichardKnop/machinery/v2/brokers/redis\"\n\texampletasks \"github.com/RichardKnop/machinery/v2/example/tasks\"\n\t\"github.com/RichardKnop/machinery/v2/example/tracers\"\n\teagerlock \"github.com/RichardKnop/machinery/v2/locks/eager\"\n\t\"github.com/opentracing/opentracing-go\"\n\topentracinglog \"github.com/opentracing/opentracing-go/log\"\n)\n\nvar (\n\tapp *cli.App\n)\n\nfunc init() {\n\t// Initialise a CLI app\n\tapp = cli.NewApp()\n\tapp.Name = \"machinery\"\n\tapp.Usage = \"machinery worker and send example tasks with machinery send\"\n\tapp.Version = \"0.0.0\"\n}\n\nfunc main() {\n\t// Set the CLI app commands\n\tapp.Commands = []cli.Command{\n\t\t{\n\t\t\tName:  \"worker\",\n\t\t\tUsage: \"launch machinery worker\",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := worker(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:  \"send\",\n\t\t\tUsage: \"send example tasks \",\n\t\t\tAction: func(c *cli.Context) error {\n\t\t\t\tif err := send(); err != nil {\n\t\t\t\t\treturn cli.NewExitError(err.Error(), 1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t}\n\n\t// Run the CLI app\n\t_ = app.Run(os.Args)\n}\n\nfunc startServer() (*machinery.Server, error) {\n\tcnf := &config.Config{\n\t\tDefaultQueue:    \"machinery_tasks\",\n\t\tResultsExpireIn: 3600,\n\t\tRedis: &config.RedisConfig{\n\t\t\tMaxIdle:                3,\n\t\t\tIdleTimeout:            240,\n\t\t\tReadTimeout:            15,\n\t\t\tWriteTimeout:           15,\n\t\t\tConnectTimeout:         15,\n\t\t\tNormalTasksPollPeriod:  1000,\n\t\t\tDelayedTasksPollPeriod: 500,\n\t\t},\n\t}\n\n\t// Create server instance\n\tbroker := redisbroker.New(cnf, \"localhost:6379\", \"\", \"\", \"\", 0)\n\tbackend := redisbackend.New(cnf, \"localhost:6379\", \"\", \"\", \"\", 0)\n\tlock := eagerlock.New()\n\tserver := machinery.NewServer(cnf, broker, backend, lock)\n\n\t// Register tasks\n\ttasksMap := map[string]interface{}{\n\t\t\"add\":               exampletasks.Add,\n\t\t\"multiply\":          exampletasks.Multiply,\n\t\t\"sum_ints\":          exampletasks.SumInts,\n\t\t\"sum_floats\":        exampletasks.SumFloats,\n\t\t\"concat\":            exampletasks.Concat,\n\t\t\"split\":             exampletasks.Split,\n\t\t\"panic_task\":        exampletasks.PanicTask,\n\t\t\"long_running_task\": exampletasks.LongRunningTask,\n\t}\n\n\treturn server, server.RegisterTasks(tasksMap)\n}\n\nfunc worker() error {\n\tconsumerTag := \"machinery_worker\"\n\n\tcleanup, err := tracers.SetupTracer(consumerTag)\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// The second argument is a consumer tag\n\t// Ideally, each worker should have a unique tag (worker1, worker2 etc)\n\tworker := server.NewWorker(consumerTag, 0)\n\n\t// Here we inject some custom code for error handling,\n\t// start and end of task hooks, useful for metrics for example.\n\terrorHandler := func(err error) {\n\t\tlog.ERROR.Println(\"I am an error handler:\", err)\n\t}\n\n\tpreTaskHandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am a start of task handler for:\", signature.Name)\n\t}\n\n\tpostTaskHandler := func(signature *tasks.Signature) {\n\t\tlog.INFO.Println(\"I am an end of task handler for:\", signature.Name)\n\t}\n\n\tworker.SetPostTaskHandler(postTaskHandler)\n\tworker.SetErrorHandler(errorHandler)\n\tworker.SetPreTaskHandler(preTaskHandler)\n\n\treturn worker.Launch()\n}\n\nfunc send() error {\n\tcleanup, err := tracers.SetupTracer(\"sender\")\n\tif err != nil {\n\t\tlog.FATAL.Fatalln(\"Unable to instantiate a tracer:\", err)\n\t}\n\tdefer cleanup()\n\n\tserver, err := startServer()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar (\n\t\taddTask0, addTask1, addTask2                      tasks.Signature\n\t\tmultiplyTask0, multiplyTask1                      tasks.Signature\n\t\tsumIntsTask, sumFloatsTask, concatTask, splitTask tasks.Signature\n\t\tpanicTask                                         tasks.Signature\n\t\tlongRunningTask                                   tasks.Signature\n\t)\n\n\tvar initTasks = func() {\n\t\taddTask0 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask1 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\taddTask2 = tasks.Signature{\n\t\t\tName: \"add\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 5,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 6,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask0 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"int64\",\n\t\t\t\t\tValue: 4,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmultiplyTask1 = tasks.Signature{\n\t\t\tName: \"multiply\",\n\t\t}\n\n\t\tsumIntsTask = tasks.Signature{\n\t\t\tName: \"sum_ints\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]int64\",\n\t\t\t\t\tValue: []int64{1, 2},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsumFloatsTask = tasks.Signature{\n\t\t\tName: \"sum_floats\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]float64\",\n\t\t\t\t\tValue: []float64{1.5, 2.7},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tconcatTask = tasks.Signature{\n\t\t\tName: \"concat\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"[]string\",\n\t\t\t\t\tValue: []string{\"foo\", \"bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tsplitTask = tasks.Signature{\n\t\t\tName: \"split\",\n\t\t\tArgs: []tasks.Arg{\n\t\t\t\t{\n\t\t\t\t\tType:  \"string\",\n\t\t\t\t\tValue: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tpanicTask = tasks.Signature{\n\t\t\tName: \"panic_task\",\n\t\t}\n\n\t\tlongRunningTask = tasks.Signature{\n\t\t\tName: \"long_running_task\",\n\t\t}\n\t}\n\n\t/*\n\t * Lets start a span representing this run of the `send` command and\n\t * set a batch id as baggage so it can travel all the way into\n\t * the worker functions.\n\t */\n\tspan, ctx := opentracing.StartSpanFromContext(context.Background(), \"send\")\n\tdefer span.Finish()\n\n\tbatchID := uuid.New().String()\n\tspan.SetBaggageItem(\"batch.id\", batchID)\n\tspan.LogFields(opentracinglog.String(\"batch.id\", batchID))\n\n\tlog.INFO.Println(\"Starting batch:\", batchID)\n\t/*\n\t * First, let's try sending a single task\n\t */\n\tinitTasks()\n\n\tlog.INFO.Println(\"Single task:\")\n\n\tasyncResult, err := server.SendTaskWithContext(ctx, &addTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err := asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"1 + 1 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Try couple of tasks with a slice argument and slice return value\n\t */\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumIntsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1, 2]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &sumFloatsTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"sum([1.5, 2.7]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &concatTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"concat([\\\"foo\\\", \\\"bar\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\tasyncResult, err = server.SendTaskWithContext(ctx, &splitTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"split([\\\"foo\\\"]) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t/*\n\t * Now let's explore ways of sending multiple tasks\n\t */\n\n\t// Now let's try a parallel execution\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks (parallel execution):\")\n\n\tgroup, err := tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tasyncResults, err := server.SendGroupWithContext(ctx, group, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send group: %s\", err.Error())\n\t}\n\n\tfor _, asyncResult := range asyncResults {\n\t\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Getting task result failed with error: %s\", err.Error())\n\t\t}\n\t\tlog.INFO.Printf(\n\t\t\t\"%v + %v = %v\\n\",\n\t\t\tasyncResult.Signature.Args[0].Value,\n\t\t\tasyncResult.Signature.Args[1].Value,\n\t\t\ttasks.HumanReadableResults(results),\n\t\t)\n\t}\n\n\t// Now let's try a group with a chord\n\tinitTasks()\n\tlog.INFO.Println(\"Group of tasks with a callback (chord):\")\n\n\tgroup, err = tasks.NewGroup(&addTask0, &addTask1, &addTask2)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating group: %s\", err.Error())\n\t}\n\n\tchord, err := tasks.NewChord(group, &multiplyTask1)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chord: %s\", err)\n\t}\n\n\tchordAsyncResult, err := server.SendChordWithContext(ctx, chord, 10)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chord: %s\", err.Error())\n\t}\n\n\tresults, err = chordAsyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chord result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(1 + 1) * (2 + 2) * (5 + 6) = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Now let's try chaining task results\n\tinitTasks()\n\tlog.INFO.Println(\"Chain of tasks:\")\n\n\tchain, err := tasks.NewChain(&addTask0, &addTask1, &addTask2, &multiplyTask0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating chain: %s\", err)\n\t}\n\n\tchainAsyncResult, err := server.SendChainWithContext(ctx, chain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send chain: %s\", err.Error())\n\t}\n\n\tresults, err = chainAsyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting chain result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"(((1 + 1) + (2 + 2)) + (5 + 6)) * 4 = %v\\n\", tasks.HumanReadableResults(results))\n\n\t// Let's try a task which throws panic to make sure stack trace is not lost\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &panicTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\t_, err = asyncResult.Get(time.Millisecond * 5)\n\tif err == nil {\n\t\treturn errors.New(\"Error should not be nil if task panicked\")\n\t}\n\tlog.INFO.Printf(\"Task panicked and returned error = %v\\n\", err.Error())\n\n\t// Let's try a long running task\n\tinitTasks()\n\tasyncResult, err = server.SendTaskWithContext(ctx, &longRunningTask)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not send task: %s\", err.Error())\n\t}\n\n\tresults, err = asyncResult.Get(time.Millisecond * 5)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Getting long running task result failed with error: %s\", err.Error())\n\t}\n\tlog.INFO.Printf(\"Long running task returned = %v\\n\", tasks.HumanReadableResults(results))\n\n\treturn nil\n}\n"
  },
  {
    "path": "v2/example/tasks/tasks.go",
    "content": "package exampletasks\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/log\"\n)\n\n// Add ...\nfunc Add(args ...int64) (int64, error) {\n\tsum := int64(0)\n\tfor _, arg := range args {\n\t\tsum += arg\n\t}\n\treturn sum, nil\n}\n\n// Multiply ...\nfunc Multiply(args ...int64) (int64, error) {\n\tsum := int64(1)\n\tfor _, arg := range args {\n\t\tsum *= arg\n\t}\n\treturn sum, nil\n}\n\n// SumInts ...\nfunc SumInts(numbers []int64) (int64, error) {\n\tvar sum int64\n\tfor _, num := range numbers {\n\t\tsum += num\n\t}\n\treturn sum, nil\n}\n\n// SumFloats ...\nfunc SumFloats(numbers []float64) (float64, error) {\n\tvar sum float64\n\tfor _, num := range numbers {\n\t\tsum += num\n\t}\n\treturn sum, nil\n}\n\n// Concat ...\nfunc Concat(strs []string) (string, error) {\n\tvar res string\n\tfor _, s := range strs {\n\t\tres += s\n\t}\n\treturn res, nil\n}\n\n// Split ...\nfunc Split(str string) ([]string, error) {\n\treturn strings.Split(str, \"\"), nil\n}\n\n// PanicTask ...\nfunc PanicTask() (string, error) {\n\tpanic(errors.New(\"oops\"))\n}\n\n// LongRunningTask ...\nfunc LongRunningTask() error {\n\tlog.INFO.Print(\"Long running task started\")\n\tfor i := 0; i < 10; i++ {\n\t\tlog.INFO.Print(10 - i)\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\tlog.INFO.Print(\"Long running task finished\")\n\treturn nil\n}\n"
  },
  {
    "path": "v2/example/tracers/jaeger.go",
    "content": "package tracers\n\n// Uncomment the import statement for the jaeger tracer.\n// make sure you run dep ensure to pull in the jaeger client\n//\n// import (\n// \tjaeger \"github.com/uber/jaeger-client-go\"\n// \tjaegercfg \"github.com/uber/jaeger-client-go/config\"\n// )\n\n// SetupTracer is the place where you'd setup your specific tracer.\n// The jaeger tracer is given as an example.\n// To capture the jaeger traces you should run the jaeger backend.\n// This can be done using the following docker command:\n//\n// `docker run -ti --rm -p6831:6831/udp -p16686:16686 jaegertracing/all-in-one:latest`\n//\n// The collector will be listening on localhost:6831\n// and the query UI is reachable on localhost:16686.\nfunc SetupTracer(serviceName string) (func(), error) {\n\n\t// Jaeger setup code\n\t//\n\t// config := jaegercfg.Configuration{\n\t// \tSampler: &jaegercfg.SamplerConfig{\n\t// \t\tType:  jaeger.SamplerTypeConst,\n\t// \t\tParam: 1,\n\t// \t},\n\t// }\n\n\t// closer, err := config.InitGlobalTracer(serviceName)\n\t// if err != nil {\n\t// \treturn nil, err\n\t// }\n\n\tcleanupFunc := func() {\n\t\t// closer.Close()\n\t}\n\n\treturn cleanupFunc, nil\n}\n"
  },
  {
    "path": "v2/go.mod",
    "content": "module github.com/RichardKnop/machinery/v2\n\ngo 1.22\n\ntoolchain go1.24.1\n\nrequire (\n\tcloud.google.com/go/pubsub v1.10.0\n\tgithub.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae\n\tgithub.com/aws/aws-sdk-go-v2 v1.38.1\n\tgithub.com/aws/aws-sdk-go-v2/config v1.29.14\n\tgithub.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.18.12\n\tgithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.42.4\n\tgithub.com/aws/aws-sdk-go-v2/service/sqs v1.38.5\n\tgithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b\n\tgithub.com/go-redsync/redsync/v4 v4.8.1\n\tgithub.com/gomodule/redigo v1.9.2\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/kelseyhightower/envconfig v1.4.0\n\tgithub.com/opentracing/opentracing-go v1.2.0\n\tgithub.com/rabbitmq/amqp091-go v1.10.0\n\tgithub.com/redis/go-redis/v9 v9.12.1\n\tgithub.com/robfig/cron/v3 v3.0.1\n\tgithub.com/stretchr/testify v1.10.0\n\tgithub.com/urfave/cli v1.22.5\n\tgo.mongodb.org/mongo-driver v1.17.0\n\tgopkg.in/yaml.v2 v2.4.0\n)\n\nrequire (\n\tcloud.google.com/go v0.75.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect\n\tgithub.com/aws/smithy-go v1.22.5 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect\n\tgithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/golang/snappy v0.0.4 // indirect\n\tgithub.com/google/go-cmp v0.6.0 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.0.5 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/jstemmer/go-junit-report v0.9.1 // indirect\n\tgithub.com/klauspost/compress v1.13.6 // indirect\n\tgithub.com/montanaflynn/stats v0.7.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.0.1 // indirect\n\tgithub.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/scram v1.1.2 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.4 // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect\n\tgo.opencensus.io v0.22.5 // indirect\n\tgolang.org/x/crypto v0.26.0 // indirect\n\tgolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect\n\tgolang.org/x/mod v0.17.0 // indirect\n\tgolang.org/x/net v0.25.0 // indirect\n\tgolang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 // indirect\n\tgolang.org/x/sync v0.8.0 // indirect\n\tgolang.org/x/sys v0.23.0 // indirect\n\tgolang.org/x/text v0.17.0 // indirect\n\tgolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect\n\tgoogle.golang.org/api v0.39.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea // indirect\n\tgoogle.golang.org/grpc v1.35.0 // indirect\n\tgoogle.golang.org/protobuf v1.26.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\nreplace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999\n"
  },
  {
    "path": "v2/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY=\ncloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/pubsub v1.10.0 h1:JK22g5uNpscGPthjJE/D0siWtA6UlU4Cb6pLcyJkzyQ=\ncloud.google.com/go/pubsub v1.10.0/go.mod h1:eNpTrkOy7dCpkNyaSNetMa6udbgecJMd0ZsTJS/cuNo=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae h1:DcFpTQBYQ9Ct2d6sC7ol0/ynxc2pO1cpGUM+f4t5adg=\ngithub.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae/go.mod h1:rJJ84PyA/Wlmw1hO+xTzV2wsSUon6J5ktg0g8BF2PuU=\ngithub.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8=\ngithub.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=\ngithub.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=\ngithub.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ=\ngithub.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.18.12 h1:mwAIR3fhxhSzXFj530LNCBe0JocYVQx6GuJpQiA+QOs=\ngithub.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.18.12/go.mod h1:9cWrNL8q7ApFmZzKhnb63ub4zrdMzOGQVn/kxvagfeE=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=\ngithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.42.4 h1:5GjCSGIpndYU/tVABz+4XnAcluU6wrjlPzAAgFUDG98=\ngithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.42.4/go.mod h1:yYaWRnVSPyAmexW5t7G3TcuYoalYfT+xQwzWsvtUQ7M=\ngithub.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.3 h1:GHC1WTF3ZBZy+gvz2qtYB6ttALVx35hlwc4IzOIUY7g=\ngithub.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.3/go.mod h1:lUqWdw5/esjPTkITXhN4C66o1ltwDq2qQ12j3SOzhVg=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15 h1:M1R1rud7HzDrfCdlBQ7NjnRsDNEhXO/vGhuD189Ggmk=\ngithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15/go.mod h1:uvFKBSq9yMPV4LGAi7N4awn4tLY+hKE35f8THes2mzQ=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=\ngithub.com/aws/aws-sdk-go-v2/service/sqs v1.38.5 h1:KNgVWw8qbPzjYnIF1gL0EAszy6VKGnmUK6VSm1huYY8=\ngithub.com/aws/aws-sdk-go-v2/service/sqs v1.38.5/go.mod h1:Bar4MrRxeqdn6XIh8JGfiXuFRmyrrsZNTJotxEJmWW0=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=\ngithub.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=\ngithub.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=\ngithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=\ngithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=\ngithub.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=\ngithub.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=\ngithub.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=\ngithub.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=\ngithub.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=\ngithub.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=\ngithub.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=\ngithub.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=\ngithub.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=\ngithub.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=\ngithub.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=\ngithub.com/go-redsync/redsync/v4 v4.8.1 h1:rq2RvdTI0obznMdxKUWGdmmulo7lS9yCzb8fgDKOlbM=\ngithub.com/go-redsync/redsync/v4 v4.8.1/go.mod h1:LmUAsQuQxhzZAoGY7JS6+dNhNmZyonMZiiEDY9plotM=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=\ngithub.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=\ngithub.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=\ngithub.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=\ngithub.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=\ngithub.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=\ngithub.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=\ngithub.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=\ngithub.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=\ngithub.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=\ngithub.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=\ngithub.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=\ngithub.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=\ngithub.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=\ngithub.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=\ngithub.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=\ngithub.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=\ngithub.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r40k=\ngo.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=\ngolang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 h1:BaN3BAqnopnKjvl+15DYP6LLrbBHfbfmlFYzmFj/Q9Q=\ngolang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=\ngolang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=\ngolang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=\ngolang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.39.0 h1:zHCTXf0NeDdKTgcSQpT+ZflWAqHsEp1GmdpxW09f3YM=\ngoogle.golang.org/api v0.39.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea h1:N98SvVh7Hdle2lgUVFuIkf0B3u29CUakMUQa7Hwz8Wc=\ngoogle.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "v2/integration-tests/amqp_amqp_test.go",
    "content": "package integration_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\n\tamqpbackend \"github.com/RichardKnop/machinery/v2/backends/amqp\"\n\tamqpbroker \"github.com/RichardKnop/machinery/v2/brokers/amqp\"\n\teagerlock \"github.com/RichardKnop/machinery/v2/locks/eager\"\n)\n\nfunc TestAmqpAmqp(t *testing.T) {\n\tamqpURL := os.Getenv(\"AMQP_URL\")\n\tif amqpURL == \"\" {\n\t\tt.Skip(\"AMQP_URL is not defined\")\n\t}\n\n\tfinalAmqpURL := amqpURL\n\tvar finalSeparator string\n\n\tamqpURLs := os.Getenv(\"AMQP_URLS\")\n\tif amqpURLs != \"\" {\n\t\tseparator := os.Getenv(\"AMQP_URLS_SEPARATOR\")\n\t\tif separator == \"\" {\n\t\t\treturn\n\t\t}\n\t\tfinalSeparator = separator\n\t\tfinalAmqpURL = amqpURLs\n\t}\n\n\tcnf := &config.Config{\n\t\tBroker:                  finalAmqpURL,\n\t\tMultipleBrokerSeparator: finalSeparator,\n\t\tDefaultQueue:            \"machinery_tasks\",\n\t\tResultBackend:           amqpURL,\n\t\tResultsExpireIn:         3600,\n\t\tAMQP: &config.AMQPConfig{\n\t\t\tExchange:      \"test_exchange\",\n\t\t\tExchangeType:  \"direct\",\n\t\t\tBindingKey:    \"test_task\",\n\t\t\tPrefetchCount: 1,\n\t\t},\n\t}\n\n\tbroker := amqpbroker.New(cnf)\n\tbackend := amqpbackend.New(cnf)\n\tlock := eagerlock.New()\n\tserver := machinery.NewServer(cnf, broker, backend, lock)\n\n\tregisterTestTasks(server)\n\n\tworker := server.NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "v2/integration-tests/redis_redis_test.go",
    "content": "package integration_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n\tredisbackend \"github.com/RichardKnop/machinery/v2/backends/redis\"\n\tredisbroker \"github.com/RichardKnop/machinery/v2/brokers/redis\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\teagerlock \"github.com/RichardKnop/machinery/v2/locks/eager\"\n)\n\nfunc TestRedisRedis_GoRedis(t *testing.T) {\n\tredisURL := os.Getenv(\"REDIS_URL\")\n\tif redisURL == \"\" {\n\t\tt.Skip(\"REDIS_URL is not defined\")\n\t}\n\n\tcnf := &config.Config{\n\t\tDefaultQueue:    \"machinery_tasks\",\n\t\tResultsExpireIn: 3600,\n\t\tRedis: &config.RedisConfig{\n\t\t\tMaxIdle:                3,\n\t\t\tIdleTimeout:            240,\n\t\t\tReadTimeout:            15,\n\t\t\tWriteTimeout:           15,\n\t\t\tConnectTimeout:         15,\n\t\t\tNormalTasksPollPeriod:  1000,\n\t\t\tDelayedTasksPollPeriod: 500,\n\t\t},\n\t}\n\n\tbroker := redisbroker.NewGR(cnf, []string{redisURL}, 0)\n\tbackend := redisbackend.NewGR(cnf, []string{redisURL}, 0)\n\tlock := eagerlock.New()\n\tserver := machinery.NewServer(cnf, broker, backend, lock)\n\n\tregisterTestTasks(server)\n\n\tworker := server.NewWorker(\"test_worker\", 0)\n\tdefer worker.Quit()\n\tgo worker.Launch()\n\ttestAll(server, t)\n}\n"
  },
  {
    "path": "v2/integration-tests/suite_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"reflect\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/result\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\n\tbrokersiface \"github.com/RichardKnop/machinery/v2/brokers/iface\"\n)\n\ntype ascendingInt64s []int64\n\nfunc (a ascendingInt64s) Len() int           { return len(a) }\nfunc (a ascendingInt64s) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }\nfunc (a ascendingInt64s) Less(i, j int) bool { return a[i] < a[j] }\n\ntype Server interface {\n\tGetBroker() brokersiface.Broker\n\tGetConfig() *config.Config\n\tRegisterTasks(namedTaskFuncs map[string]interface{}) error\n\tSendTaskWithContext(ctx context.Context, signature *tasks.Signature) (*result.AsyncResult, error)\n\tSendTask(signature *tasks.Signature) (*result.AsyncResult, error)\n\tSendChainWithContext(ctx context.Context, chain *tasks.Chain) (*result.ChainAsyncResult, error)\n\tSendChain(chain *tasks.Chain) (*result.ChainAsyncResult, error)\n\tSendGroupWithContext(ctx context.Context, group *tasks.Group, sendConcurrency int) ([]*result.AsyncResult, error)\n\tSendGroup(group *tasks.Group, sendConcurrency int) ([]*result.AsyncResult, error)\n\tSendChordWithContext(ctx context.Context, chord *tasks.Chord, sendConcurrency int) (*result.ChordAsyncResult, error)\n\tSendChord(chord *tasks.Chord, sendConcurrency int) (*result.ChordAsyncResult, error)\n}\n\nfunc testAll(server Server, t *testing.T) {\n\ttestSendTask(server, t)\n\ttestSendGroup(server, t, 0) // with unlimited concurrency\n\ttestSendGroup(server, t, 2) // with limited concurrency (2 parallel tasks at the most)\n\ttestSendChord(server, t)\n\ttestSendChain(server, t)\n\ttestReturnJustError(server, t)\n\ttestReturnMultipleValues(server, t)\n\ttestPanic(server, t)\n\ttestDelay(server, t)\n}\n\nfunc testSendTask(server Server, t *testing.T) {\n\taddTask := newAddTask(1, 1)\n\n\tasyncResult, err := server.SendTask(addTask)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\tif results[0].Interface() != int64(2) {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want int64(2)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n\n\tsumTask := newSumTask([]int64{1, 2})\n\tasyncResult, err = server.SendTask(sumTask)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\tif results[0].Interface() != int64(3) {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want int64(3)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n}\n\nfunc testSendGroup(server Server, t *testing.T, sendConcurrency int) {\n\tt1, t2, t3 := newAddTask(1, 1), newAddTask(2, 2), newAddTask(5, 6)\n\n\tgroup, err := tasks.NewGroup(t1, t2, t3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tasyncResults, err := server.SendGroup(group, sendConcurrency)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\texpectedResults := []int64{2, 4, 11}\n\n\tactualResults := make([]int64, 3)\n\n\tfor i, asyncResult := range asyncResults {\n\t\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif len(results) != 1 {\n\t\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t\t}\n\n\t\tintResult, ok := results[0].Interface().(int64)\n\t\tif !ok {\n\t\t\tt.Errorf(\"Could not convert %v to int64\", results[0].Interface())\n\t\t}\n\t\tactualResults[i] = intResult\n\t}\n\n\tsort.Sort(ascendingInt64s(actualResults))\n\n\tif !reflect.DeepEqual(expectedResults, actualResults) {\n\t\tt.Errorf(\n\t\t\t\"expected results = %v, actual results = %v\",\n\t\t\texpectedResults,\n\t\t\tactualResults,\n\t\t)\n\t}\n}\n\nfunc testSendChain(server Server, t *testing.T) {\n\tt1, t2, t3 := newAddTask(2, 2), newAddTask(5, 6), newMultipleTask(4)\n\n\tchain, err := tasks.NewChain(t1, t2, t3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tchainAsyncResult, err := server.SendChain(chain)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := chainAsyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\tif results[0].Interface() != int64(60) {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want int64(60)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n}\n\nfunc testSendChord(server Server, t *testing.T) {\n\tt1, t2, t3, t4 := newAddTask(1, 1), newAddTask(2, 2), newAddTask(5, 6), newMultipleTask()\n\n\tgroup, err := tasks.NewGroup(t1, t2, t3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tchord, err := tasks.NewChord(group, t4)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tchordAsyncResult, err := server.SendChord(chord, 10)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := chordAsyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\tif results[0].Interface() != int64(88) {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want int64(88)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n}\n\nfunc testReturnJustError(server Server, t *testing.T) {\n\t// Fails, returns error as the only value\n\ttask := newErrorTask(\"Test error\", true)\n\tasyncResult, err := server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif len(results) != 0 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 0)\n\t}\n\tassert.Equal(t, \"Test error\", err.Error())\n\n\t// Successful, returns nil as the only value\n\ttask = newErrorTask(\"\", false)\n\tasyncResult, err = server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif len(results) != 0 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 0)\n\t}\n\tassert.NoError(t, err)\n}\n\nfunc testReturnMultipleValues(server Server, t *testing.T) {\n\t// Successful task with multiple return values\n\ttask := newMultipleReturnTask(\"foo\", \"bar\", false)\n\n\tasyncResult, err := server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 2 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 2)\n\t}\n\n\tif results[0].Interface() != \"foo\" {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want string(\\\"foo\\\":)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n\n\tif results[1].Interface() != \"bar\" {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want string(\\\"bar\\\":)\",\n\t\t\tresults[1].Type().String(),\n\t\t\tresults[1].Interface(),\n\t\t)\n\t}\n\n\t// Failed task with multiple return values\n\ttask = newMultipleReturnTask(\"\", \"\", true)\n\n\tasyncResult, err = server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err = asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif len(results) != 0 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 0)\n\t}\n\tassert.Error(t, err)\n}\n\nfunc testPanic(server Server, t *testing.T) {\n\ttask := &tasks.Signature{Name: \"panic\"}\n\tasyncResult, err := server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(time.Millisecond * 5))\n\tif len(results) != 0 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 0)\n\t}\n\tassert.Equal(t, \"oops\", err.Error())\n}\n\nfunc testDelay(server Server, t *testing.T) {\n\tnow := time.Now().UTC()\n\teta := now.Add(100 * time.Millisecond)\n\ttask := newDelayTask(eta)\n\tasyncResult, err := server.SendTask(task)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresults, err := asyncResult.Get(time.Duration(5 * time.Millisecond))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Errorf(\"Number of results returned = %d. Wanted %d\", len(results), 1)\n\t}\n\n\ttm, ok := results[0].Interface().(int64)\n\tif !ok {\n\t\tt.Errorf(\n\t\t\t\"Could not type assert = %v(%v) to int64\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t)\n\t}\n\n\tif tm < eta.UnixNano() {\n\t\tt.Errorf(\n\t\t\t\"result = %v(%v), want >= int64(%d)\",\n\t\t\tresults[0].Type().String(),\n\t\t\tresults[0].Interface(),\n\t\t\teta.UnixNano(),\n\t\t)\n\t}\n}\n\nfunc registerTestTasks(server Server) {\n\n\ttasks := map[string]interface{}{\n\t\t\"add\": func(args ...int64) (int64, error) {\n\t\t\tsum := int64(0)\n\t\t\tfor _, arg := range args {\n\t\t\t\tsum += arg\n\t\t\t}\n\t\t\treturn sum, nil\n\t\t},\n\t\t\"multiply\": func(args ...int64) (int64, error) {\n\t\t\tsum := int64(1)\n\t\t\tfor _, arg := range args {\n\t\t\t\tsum *= arg\n\t\t\t}\n\t\t\treturn sum, nil\n\t\t},\n\t\t\"sum\": func(numbers []int64) (int64, error) {\n\t\t\tvar sum int64\n\t\t\tfor _, num := range numbers {\n\t\t\t\tsum += num\n\t\t\t}\n\t\t\treturn sum, nil\n\t\t},\n\t\t\"return_just_error\": func(msg string, fail bool) (err error) {\n\t\t\tif fail {\n\t\t\t\terr = errors.New(msg)\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\t\"return_multiple_values\": func(arg1, arg2 string, fail bool) (r1 string, r2 string, err error) {\n\t\t\tif fail {\n\t\t\t\terr = errors.New(\"some error\")\n\t\t\t} else {\n\t\t\t\tr1 = arg1\n\t\t\t\tr2 = arg2\n\t\t\t}\n\t\t\treturn r1, r2, err\n\t\t},\n\t\t\"panic\": func() (string, error) {\n\t\t\tpanic(errors.New(\"oops\"))\n\t\t},\n\t\t\"delay_test\": func() (int64, error) {\n\t\t\treturn time.Now().UTC().UnixNano(), nil\n\t\t},\n\t}\n\n\tserver.RegisterTasks(tasks)\n}\n\nfunc newAddTask(a, b int) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"add\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: a,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"int64\",\n\t\t\t\tValue: b,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc newMultipleTask(nums ...int) *tasks.Signature {\n\targs := make([]tasks.Arg, len(nums))\n\tfor i, n := range nums {\n\t\targs[i] = tasks.Arg{\n\t\t\tType:  \"int64\",\n\t\t\tValue: n,\n\t\t}\n\t}\n\treturn &tasks.Signature{\n\t\tName: \"multiply\",\n\t\tArgs: args,\n\t}\n}\n\nfunc newSumTask(nums []int64) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"sum\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"[]int64\",\n\t\t\t\tValue: nums,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc newErrorTask(msg string, fail bool) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"return_just_error\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"string\",\n\t\t\t\tValue: msg,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"bool\",\n\t\t\t\tValue: fail,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc newMultipleReturnTask(arg1, arg2 string, fail bool) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"return_multiple_values\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"string\",\n\t\t\t\tValue: arg1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"string\",\n\t\t\t\tValue: arg2,\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"bool\",\n\t\t\t\tValue: fail,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc newDelayTask(eta time.Time) *tasks.Signature {\n\treturn &tasks.Signature{\n\t\tName: \"delay_test\",\n\t\tETA:  &eta,\n\t}\n}\n"
  },
  {
    "path": "v2/locks/eager/eager.go",
    "content": "package eager\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\tErrEagerLockFailed = errors.New(\"eager lock: failed to acquire lock\")\n)\n\ntype Lock struct {\n\tretries  int\n\tinterval time.Duration\n\tregister struct {\n\t\tsync.RWMutex\n\t\tm map[string]int64\n\t}\n}\n\nfunc New() *Lock {\n\treturn &Lock{\n\t\tretries:  3,\n\t\tinterval: 5 * time.Second,\n\t\tregister: struct {\n\t\t\tsync.RWMutex\n\t\t\tm map[string]int64\n\t\t}{m: make(map[string]int64)},\n\t}\n}\n\nfunc (e *Lock) LockWithRetries(key string, value int64) error {\n\tfor i := 0; i <= e.retries; i++ {\n\t\terr := e.Lock(key, value)\n\t\tif err == nil {\n\t\t\t//成功拿到锁，返回\n\t\t\treturn nil\n\t\t}\n\n\t\ttime.Sleep(e.interval)\n\t}\n\treturn ErrEagerLockFailed\n}\n\nfunc (e *Lock) Lock(key string, value int64) error {\n\te.register.Lock()\n\tdefer e.register.Unlock()\n\ttimeout, exist := e.register.m[key]\n\tif !exist || time.Now().UnixNano() > timeout {\n\t\te.register.m[key] = value\n\t\treturn nil\n\t}\n\treturn ErrEagerLockFailed\n}\n"
  },
  {
    "path": "v2/locks/eager/eager_test.go",
    "content": "package eager\n\nimport (\n\tlockiface \"github.com/RichardKnop/machinery/v2/locks/iface\"\n\t\"github.com/RichardKnop/machinery/v2/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestLock_Lock(t *testing.T) {\n\tlock := New()\n\tkeyName := utils.GetPureUUID()\n\n\tgo func() {\n\t\terr := lock.Lock(keyName, time.Now().Add(25*time.Second).UnixNano())\n\t\tassert.NoError(t, err)\n\t}()\n\ttime.Sleep(1 * time.Second)\n\terr := lock.Lock(keyName, time.Now().Add(25*time.Second).UnixNano())\n\tassert.Error(t, err)\n\tassert.EqualError(t, err, ErrEagerLockFailed.Error())\n}\n\nfunc TestLock_LockWithRetries(t *testing.T) {\n\tlock := New()\n\tkeyName := utils.GetPureUUID()\n\n\tgo func() {\n\t\terr := lock.LockWithRetries(keyName, time.Now().Add(25*time.Second).UnixNano())\n\t\tassert.NoError(t, err)\n\t}()\n\ttime.Sleep(1 * time.Second)\n\terr := lock.LockWithRetries(keyName, time.Now().Add(25*time.Second).UnixNano())\n\tassert.Error(t, err)\n\tassert.EqualError(t, err, ErrEagerLockFailed.Error())\n}\n\nfunc TestNew(t *testing.T) {\n\tlock := New()\n\tassert.Implements(t, (*lockiface.Lock)(nil), lock)\n}\n"
  },
  {
    "path": "v2/locks/iface/interfaces.go",
    "content": "package iface\n\ntype Lock interface {\n\t//Acquire the lock with retry\n\t//key: the name of the lock,\n\t//value: at the nanosecond timestamp that lock needs to be released automatically\n\tLockWithRetries(key string, value int64) error\n\n\t//Acquire the lock with once\n\t//key: the name of the lock,\n\t//value: at the nanosecond timestamp that lock needs to be released automatically\n\tLock(key string, value int64) error\n}\n"
  },
  {
    "path": "v2/locks/redis/redis.go",
    "content": "package redis\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/redis/go-redis/v9\"\n\n\t\"github.com/RichardKnop/machinery/v2/config\"\n)\n\nvar (\n\tErrRedisLockFailed = errors.New(\"redis lock: failed to acquire lock\")\n)\n\ntype Lock struct {\n\trclient  redis.UniversalClient\n\tretries  int\n\tinterval time.Duration\n}\n\nfunc New(cnf *config.Config, addrs []string, db, retries int) Lock {\n\tif retries <= 0 {\n\t\treturn Lock{}\n\t}\n\tlock := Lock{retries: retries}\n\n\tvar password string\n\n\tparts := strings.Split(addrs[0], \"@\")\n\tif len(parts) >= 2 {\n\t\tpassword = strings.Join(parts[:len(parts)-1], \"@\")\n\t\taddrs[0] = parts[len(parts)-1] // addr is the last one without @\n\t}\n\n\tropt := &redis.UniversalOptions{\n\t\tAddrs:    addrs,\n\t\tDB:       db,\n\t\tPassword: password,\n\t}\n\tif cnf.Redis != nil {\n\t\tropt.MasterName = cnf.Redis.MasterName\n\t}\n\n\tif cnf.Redis != nil && cnf.Redis.SentinelPassword != \"\" {\n\t\tropt.SentinelPassword = cnf.Redis.SentinelPassword\n\t}\n\n\tif cnf.Redis != nil && cnf.Redis.ClusterEnabled {\n\t\tlock.rclient = redis.NewClusterClient(ropt.Cluster())\n\t} else {\n\t\tlock.rclient = redis.NewUniversalClient(ropt)\n\t}\n\n\treturn lock\n}\n\nfunc (r Lock) LockWithRetries(key string, unixTsToExpireNs int64) error {\n\tfor i := 0; i <= r.retries; i++ {\n\t\terr := r.Lock(key, unixTsToExpireNs)\n\t\tif err == nil {\n\t\t\t// 成功拿到锁，返回\n\t\t\treturn nil\n\t\t}\n\n\t\ttime.Sleep(r.interval)\n\t}\n\treturn ErrRedisLockFailed\n}\n\nfunc (r Lock) Lock(key string, unixTsToExpireNs int64) error {\n\tnow := time.Now().UnixNano()\n\texpiration := time.Duration(unixTsToExpireNs + 1 - now)\n\tctx := context.Background()\n\n\tsuccess, err := r.rclient.SetNX(ctx, key, unixTsToExpireNs, expiration).Result()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !success {\n\t\tv, err := r.rclient.Get(ctx, key).Result()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttimeout, err := strconv.Atoi(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif timeout != 0 && now > int64(timeout) {\n\t\t\tnewTimeout, err := r.rclient.GetSet(ctx, key, unixTsToExpireNs).Result()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcurTimeout, err := strconv.Atoi(newTimeout)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif now > int64(curTimeout) {\n\t\t\t\t// success to acquire lock with get set\n\t\t\t\t// set the expiration of redis key\n\t\t\t\tr.rclient.Expire(ctx, key, expiration)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn ErrRedisLockFailed\n\t\t}\n\n\t\treturn ErrRedisLockFailed\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "v2/log/log.go",
    "content": "package log\n\nimport (\n\t\"github.com/RichardKnop/logging\"\n)\n\nvar (\n\tlogger = logging.New(nil, nil, new(logging.ColouredFormatter))\n\n\t// DEBUG ...\n\tDEBUG = logger[logging.DEBUG]\n\t// INFO ...\n\tINFO = logger[logging.INFO]\n\t// WARNING ...\n\tWARNING = logger[logging.WARNING]\n\t// ERROR ...\n\tERROR = logger[logging.ERROR]\n\t// FATAL ...\n\tFATAL = logger[logging.FATAL]\n)\n\n// Set sets a custom logger for all log levels\nfunc Set(l logging.LoggerInterface) {\n\tDEBUG = l\n\tINFO = l\n\tWARNING = l\n\tERROR = l\n\tFATAL = l\n}\n\n// SetDebug sets a custom logger for DEBUG level logs\nfunc SetDebug(l logging.LoggerInterface) {\n\tDEBUG = l\n}\n\n// SetInfo sets a custom logger for INFO level logs\nfunc SetInfo(l logging.LoggerInterface) {\n\tINFO = l\n}\n\n// SetWarning sets a custom logger for WARNING level logs\nfunc SetWarning(l logging.LoggerInterface) {\n\tWARNING = l\n}\n\n// SetError sets a custom logger for ERROR level logs\nfunc SetError(l logging.LoggerInterface) {\n\tERROR = l\n}\n\n// SetFatal sets a custom logger for FATAL level logs\nfunc SetFatal(l logging.LoggerInterface) {\n\tFATAL = l\n}\n"
  },
  {
    "path": "v2/log/log_test.go",
    "content": "package log_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/log\"\n)\n\nfunc TestDefaultLogger(t *testing.T) {\n\tlog.INFO.Print(\"should not panic\")\n\tlog.WARNING.Print(\"should not panic\")\n\tlog.ERROR.Print(\"should not panic\")\n\tlog.FATAL.Print(\"should not panic\")\n}\n"
  },
  {
    "path": "v2/package.go",
    "content": "package machinery\n"
  },
  {
    "path": "v2/retry/fibonacci.go",
    "content": "package retry\n\n// Fibonacci returns successive Fibonacci numbers starting from 1\nfunc Fibonacci() func() int {\n\ta, b := 0, 1\n\treturn func() int {\n\t\ta, b = b, a+b\n\t\treturn a\n\t}\n}\n\n// FibonacciNext returns next number in Fibonacci sequence greater than start\nfunc FibonacciNext(start int) int {\n\tfib := Fibonacci()\n\tnum := fib()\n\tfor num <= start {\n\t\tnum = fib()\n\t}\n\treturn num\n}\n"
  },
  {
    "path": "v2/retry/fibonacci_test.go",
    "content": "package retry_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/retry\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestFibonacci(t *testing.T) {\n\tfibonacci := retry.Fibonacci()\n\n\tsequence := []int{\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t\tfibonacci(),\n\t}\n\n\tassert.EqualValues(t, sequence, []int{1, 1, 2, 3, 5, 8})\n}\n\nfunc TestFibonacciNext(t *testing.T) {\n\tassert.Equal(t, 1, retry.FibonacciNext(0))\n\tassert.Equal(t, 2, retry.FibonacciNext(1))\n\tassert.Equal(t, 5, retry.FibonacciNext(3))\n\tassert.Equal(t, 5, retry.FibonacciNext(4))\n\tassert.Equal(t, 8, retry.FibonacciNext(5))\n\tassert.Equal(t, 13, retry.FibonacciNext(8))\n}\n"
  },
  {
    "path": "v2/retry/retry.go",
    "content": "package retry\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/log\"\n)\n\n// Closure - a useful closure we can use when there is a problem\n// connecting to the broker. It uses Fibonacci sequence to space out retry attempts\nvar Closure = func() func(chan int) {\n\tretryIn := 0\n\tfibonacci := Fibonacci()\n\treturn func(stopChan chan int) {\n\t\tif retryIn > 0 {\n\t\t\tdurationString := fmt.Sprintf(\"%vs\", retryIn)\n\t\t\tduration, _ := time.ParseDuration(durationString)\n\n\t\t\tlog.WARNING.Printf(\"Retrying in %v seconds\", retryIn)\n\n\t\t\tselect {\n\t\t\tcase <-stopChan:\n\t\t\t\tbreak\n\t\t\tcase <-time.After(duration):\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tretryIn = fibonacci()\n\t}\n}\n"
  },
  {
    "path": "v2/server.go",
    "content": "package machinery\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/robfig/cron/v3\"\n\n\t\"github.com/RichardKnop/machinery/v2/backends/result\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/RichardKnop/machinery/v2/tracing\"\n\t\"github.com/RichardKnop/machinery/v2/utils\"\n\n\tbackendsiface \"github.com/RichardKnop/machinery/v2/backends/iface\"\n\tbrokersiface \"github.com/RichardKnop/machinery/v2/brokers/iface\"\n\tlockiface \"github.com/RichardKnop/machinery/v2/locks/iface\"\n\topentracing \"github.com/opentracing/opentracing-go\"\n)\n\n// Server is the main Machinery object and stores all configuration\n// All the tasks workers process are registered against the server\ntype Server struct {\n\tconfig            *config.Config\n\tregisteredTasks   *sync.Map\n\tbroker            brokersiface.Broker\n\tbackend           backendsiface.Backend\n\tlock              lockiface.Lock\n\tscheduler         *cron.Cron\n\tprePublishHandler func(*tasks.Signature)\n}\n\n// NewServer creates Server instance\nfunc NewServer(cnf *config.Config, brokerServer brokersiface.Broker, backendServer backendsiface.Backend, lock lockiface.Lock) *Server {\n\tsrv := &Server{\n\t\tconfig:          cnf,\n\t\tregisteredTasks: new(sync.Map),\n\t\tbroker:          brokerServer,\n\t\tbackend:         backendServer,\n\t\tlock:            lock,\n\t\tscheduler:       cron.New(),\n\t}\n\n\t// Run scheduler job\n\tgo srv.scheduler.Run()\n\n\treturn srv\n}\n\n// NewWorker creates Worker instance\nfunc (server *Server) NewWorker(consumerTag string, concurrency int) *Worker {\n\treturn &Worker{\n\t\tserver:      server,\n\t\tConsumerTag: consumerTag,\n\t\tConcurrency: concurrency,\n\t\tQueue:       \"\",\n\t}\n}\n\n// NewCustomQueueWorker creates Worker instance with Custom Queue\nfunc (server *Server) NewCustomQueueWorker(consumerTag string, concurrency int, queue string) *Worker {\n\treturn &Worker{\n\t\tserver:      server,\n\t\tConsumerTag: consumerTag,\n\t\tConcurrency: concurrency,\n\t\tQueue:       queue,\n\t}\n}\n\n// GetBroker returns broker\nfunc (server *Server) GetBroker() brokersiface.Broker {\n\treturn server.broker\n}\n\n// SetBroker sets broker\nfunc (server *Server) SetBroker(broker brokersiface.Broker) {\n\tserver.broker = broker\n}\n\n// GetBackend returns backend\nfunc (server *Server) GetBackend() backendsiface.Backend {\n\treturn server.backend\n}\n\n// SetBackend sets backend\nfunc (server *Server) SetBackend(backend backendsiface.Backend) {\n\tserver.backend = backend\n}\n\n// GetConfig returns connection object\nfunc (server *Server) GetConfig() *config.Config {\n\treturn server.config\n}\n\n// SetConfig sets config\nfunc (server *Server) SetConfig(cnf *config.Config) {\n\tserver.config = cnf\n}\n\n// SetPreTaskHandler Sets pre publish handler\nfunc (server *Server) SetPreTaskHandler(handler func(*tasks.Signature)) {\n\tserver.prePublishHandler = handler\n}\n\n// RegisterTasks registers all tasks at once\nfunc (server *Server) RegisterTasks(namedTaskFuncs map[string]interface{}) error {\n\tfor _, task := range namedTaskFuncs {\n\t\tif err := tasks.ValidateTask(task); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor k, v := range namedTaskFuncs {\n\t\tserver.registeredTasks.Store(k, v)\n\t}\n\tserver.broker.SetRegisteredTaskNames(server.GetRegisteredTaskNames())\n\treturn nil\n}\n\n// RegisterTask registers a single task\nfunc (server *Server) RegisterTask(name string, taskFunc interface{}) error {\n\tif err := tasks.ValidateTask(taskFunc); err != nil {\n\t\treturn err\n\t}\n\tserver.registeredTasks.Store(name, taskFunc)\n\tserver.broker.SetRegisteredTaskNames(server.GetRegisteredTaskNames())\n\treturn nil\n}\n\n// IsTaskRegistered returns true if the task name is registered with this broker\nfunc (server *Server) IsTaskRegistered(name string) bool {\n\t_, ok := server.registeredTasks.Load(name)\n\treturn ok\n}\n\n// GetRegisteredTask returns registered task by name\nfunc (server *Server) GetRegisteredTask(name string) (interface{}, error) {\n\ttaskFunc, ok := server.registeredTasks.Load(name)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"Task not registered error: %s\", name)\n\t}\n\treturn taskFunc, nil\n}\n\n// SendTaskWithContext will inject the trace context in the signature headers before publishing it\nfunc (server *Server) SendTaskWithContext(ctx context.Context, signature *tasks.Signature) (*result.AsyncResult, error) {\n\tspan, _ := opentracing.StartSpanFromContext(ctx, \"SendTask\", tracing.ProducerOption(), tracing.MachineryTag)\n\tdefer span.Finish()\n\n\t// tag the span with some info about the signature\n\tsignature.Headers = tracing.HeadersWithSpan(signature.Headers, span)\n\n\t// Make sure result backend is defined\n\tif server.backend == nil {\n\t\treturn nil, errors.New(\"Result backend required\")\n\t}\n\n\t// Auto generate a UUID if not set already\n\tif signature.UUID == \"\" {\n\t\ttaskID := uuid.New().String()\n\t\tsignature.UUID = fmt.Sprintf(\"task_%v\", taskID)\n\t}\n\n\t// Set initial task state to PENDING\n\tif err := server.backend.SetStatePending(signature); err != nil {\n\t\treturn nil, fmt.Errorf(\"Set state pending error: %s\", err)\n\t}\n\n\tif server.prePublishHandler != nil {\n\t\tserver.prePublishHandler(signature)\n\t}\n\n\tif err := server.broker.Publish(ctx, signature); err != nil {\n\t\treturn nil, fmt.Errorf(\"Publish message error: %s\", err)\n\t}\n\n\treturn result.NewAsyncResult(signature, server.backend), nil\n}\n\n// SendTask publishes a task to the default queue\nfunc (server *Server) SendTask(signature *tasks.Signature) (*result.AsyncResult, error) {\n\treturn server.SendTaskWithContext(context.Background(), signature)\n}\n\n// SendChainWithContext will inject the trace context in all the signature headers before publishing it\nfunc (server *Server) SendChainWithContext(ctx context.Context, chain *tasks.Chain) (*result.ChainAsyncResult, error) {\n\tspan, _ := opentracing.StartSpanFromContext(ctx, \"SendChain\", tracing.ProducerOption(), tracing.MachineryTag, tracing.WorkflowChainTag)\n\tdefer span.Finish()\n\n\ttracing.AnnotateSpanWithChainInfo(span, chain)\n\n\treturn server.SendChain(chain)\n}\n\n// SendChain triggers a chain of tasks\nfunc (server *Server) SendChain(chain *tasks.Chain) (*result.ChainAsyncResult, error) {\n\t_, err := server.SendTask(chain.Tasks[0])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result.NewChainAsyncResult(chain.Tasks, server.backend), nil\n}\n\n// SendGroupWithContext will inject the trace context in all the signature headers before publishing it\nfunc (server *Server) SendGroupWithContext(ctx context.Context, group *tasks.Group, sendConcurrency int) ([]*result.AsyncResult, error) {\n\tspan, _ := opentracing.StartSpanFromContext(ctx, \"SendGroup\", tracing.ProducerOption(), tracing.MachineryTag, tracing.WorkflowGroupTag)\n\tdefer span.Finish()\n\n\ttracing.AnnotateSpanWithGroupInfo(span, group, sendConcurrency)\n\n\t// Make sure result backend is defined\n\tif server.backend == nil {\n\t\treturn nil, errors.New(\"Result backend required\")\n\t}\n\n\tasyncResults := make([]*result.AsyncResult, len(group.Tasks))\n\n\tvar wg sync.WaitGroup\n\twg.Add(len(group.Tasks))\n\terrorsChan := make(chan error, len(group.Tasks)*2)\n\n\t// Init group\n\tserver.backend.InitGroup(group.GroupUUID, group.GetUUIDs())\n\n\t// Init the tasks Pending state first\n\tfor _, signature := range group.Tasks {\n\t\tif err := server.backend.SetStatePending(signature); err != nil {\n\t\t\terrorsChan <- err\n\t\t\tcontinue\n\t\t}\n\t}\n\n\tpool := make(chan struct{}, sendConcurrency)\n\tgo func() {\n\t\tfor i := 0; i < sendConcurrency; i++ {\n\t\t\tpool <- struct{}{}\n\t\t}\n\t}()\n\n\tfor i, signature := range group.Tasks {\n\n\t\tif sendConcurrency > 0 {\n\t\t\t<-pool\n\t\t}\n\n\t\tgo func(s *tasks.Signature, index int) {\n\t\t\tdefer wg.Done()\n\n\t\t\t// Publish task\n\n\t\t\terr := server.broker.Publish(ctx, s)\n\n\t\t\tif sendConcurrency > 0 {\n\t\t\t\tpool <- struct{}{}\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\terrorsChan <- fmt.Errorf(\"Publish message error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tasyncResults[index] = result.NewAsyncResult(s, server.backend)\n\t\t}(signature, i)\n\t}\n\n\tdone := make(chan int)\n\tgo func() {\n\t\twg.Wait()\n\t\tdone <- 1\n\t}()\n\n\tselect {\n\tcase err := <-errorsChan:\n\t\treturn asyncResults, err\n\tcase <-done:\n\t\treturn asyncResults, nil\n\t}\n}\n\n// SendGroup triggers a group of parallel tasks\nfunc (server *Server) SendGroup(group *tasks.Group, sendConcurrency int) ([]*result.AsyncResult, error) {\n\treturn server.SendGroupWithContext(context.Background(), group, sendConcurrency)\n}\n\n// SendChordWithContext will inject the trace context in all the signature headers before publishing it\nfunc (server *Server) SendChordWithContext(ctx context.Context, chord *tasks.Chord, sendConcurrency int) (*result.ChordAsyncResult, error) {\n\tspan, _ := opentracing.StartSpanFromContext(ctx, \"SendChord\", tracing.ProducerOption(), tracing.MachineryTag, tracing.WorkflowChordTag)\n\tdefer span.Finish()\n\n\ttracing.AnnotateSpanWithChordInfo(span, chord, sendConcurrency)\n\n\t_, err := server.SendGroupWithContext(ctx, chord.Group, sendConcurrency)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result.NewChordAsyncResult(\n\t\tchord.Group.Tasks,\n\t\tchord.Callback,\n\t\tserver.backend,\n\t), nil\n}\n\n// SendChord triggers a group of parallel tasks with a callback\nfunc (server *Server) SendChord(chord *tasks.Chord, sendConcurrency int) (*result.ChordAsyncResult, error) {\n\treturn server.SendChordWithContext(context.Background(), chord, sendConcurrency)\n}\n\n// GetRegisteredTaskNames returns slice of registered task names\nfunc (server *Server) GetRegisteredTaskNames() []string {\n\ttaskNames := make([]string, 0)\n\n\tserver.registeredTasks.Range(func(key, value interface{}) bool {\n\t\ttaskNames = append(taskNames, key.(string))\n\t\treturn true\n\t})\n\treturn taskNames\n}\n\n// RegisterPeriodicTask register a periodic task which will be triggered periodically\nfunc (server *Server) RegisterPeriodicTask(spec, name string, signature *tasks.Signature) error {\n\t//check spec\n\tschedule, err := cron.ParseStandard(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf := func() {\n\t\t//get lock\n\t\terr := server.lock.LockWithRetries(utils.GetLockName(name, spec), schedule.Next(time.Now()).UnixNano()-1)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t//send task\n\t\t_, err = server.SendTask(tasks.CopySignature(signature))\n\t\tif err != nil {\n\t\t\tlog.ERROR.Printf(\"periodic task failed. task name is: %s. error is %s\", name, err.Error())\n\t\t}\n\t}\n\n\t_, err = server.scheduler.AddFunc(spec, f)\n\treturn err\n}\n\n// RegisterPeriodicChain register a periodic chain which will be triggered periodically\nfunc (server *Server) RegisterPeriodicChain(spec, name string, signatures ...*tasks.Signature) error {\n\t//check spec\n\tschedule, err := cron.ParseStandard(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf := func() {\n\t\t// new chain\n\t\tchain, _ := tasks.NewChain(tasks.CopySignatures(signatures...)...)\n\n\t\t//get lock\n\t\terr := server.lock.LockWithRetries(utils.GetLockName(name, spec), schedule.Next(time.Now()).UnixNano()-1)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t//send task\n\t\t_, err = server.SendChain(chain)\n\t\tif err != nil {\n\t\t\tlog.ERROR.Printf(\"periodic task failed. task name is: %s. error is %s\", name, err.Error())\n\t\t}\n\t}\n\n\t_, err = server.scheduler.AddFunc(spec, f)\n\treturn err\n}\n\n// RegisterPeriodicGroup register a periodic group which will be triggered periodically\nfunc (server *Server) RegisterPeriodicGroup(spec, name string, sendConcurrency int, signatures ...*tasks.Signature) error {\n\t//check spec\n\tschedule, err := cron.ParseStandard(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf := func() {\n\t\t// new group\n\t\tgroup, _ := tasks.NewGroup(tasks.CopySignatures(signatures...)...)\n\n\t\t//get lock\n\t\terr := server.lock.LockWithRetries(utils.GetLockName(name, spec), schedule.Next(time.Now()).UnixNano()-1)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t//send task\n\t\t_, err = server.SendGroup(group, sendConcurrency)\n\t\tif err != nil {\n\t\t\tlog.ERROR.Printf(\"periodic task failed. task name is: %s. error is %s\", name, err.Error())\n\t\t}\n\t}\n\n\t_, err = server.scheduler.AddFunc(spec, f)\n\treturn err\n}\n\n// RegisterPeriodicChord register a periodic chord which will be triggered periodically\nfunc (server *Server) RegisterPeriodicChord(spec, name string, sendConcurrency int, callback *tasks.Signature, signatures ...*tasks.Signature) error {\n\t//check spec\n\tschedule, err := cron.ParseStandard(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf := func() {\n\t\t// new chord\n\t\tgroup, _ := tasks.NewGroup(tasks.CopySignatures(signatures...)...)\n\t\tchord, _ := tasks.NewChord(group, tasks.CopySignature(callback))\n\n\t\t//get lock\n\t\terr := server.lock.LockWithRetries(utils.GetLockName(name, spec), schedule.Next(time.Now()).UnixNano()-1)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t//send task\n\t\t_, err = server.SendChord(chord, sendConcurrency)\n\t\tif err != nil {\n\t\t\tlog.ERROR.Printf(\"periodic task failed. task name is: %s. error is %s\", name, err.Error())\n\t\t}\n\t}\n\n\t_, err = server.scheduler.AddFunc(spec, f)\n\treturn err\n}\n"
  },
  {
    "path": "v2/server_test.go",
    "content": "package machinery_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n\t\"github.com/RichardKnop/machinery/v2/config\"\n\n\tbackend \"github.com/RichardKnop/machinery/v2/backends/eager\"\n\tbroker \"github.com/RichardKnop/machinery/v2/brokers/eager\"\n\tlock \"github.com/RichardKnop/machinery/v2/locks/eager\"\n)\n\nfunc TestRegisterTasks(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\terr := server.RegisterTasks(map[string]interface{}{\n\t\t\"test_task\": func() error { return nil },\n\t})\n\tassert.NoError(t, err)\n\n\t_, err = server.GetRegisteredTask(\"test_task\")\n\tassert.NoError(t, err, \"test_task is not registered but it should be\")\n}\n\nfunc TestRegisterTask(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\terr := server.RegisterTask(\"test_task\", func() error { return nil })\n\tassert.NoError(t, err)\n\n\t_, err = server.GetRegisteredTask(\"test_task\")\n\tassert.NoError(t, err, \"test_task is not registered but it should be\")\n}\n\nfunc TestGetRegisteredTask(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\t_, err := server.GetRegisteredTask(\"test_task\")\n\tassert.Error(t, err, \"test_task is registered but it should not be\")\n}\n\nfunc TestGetRegisteredTaskNames(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\n\ttaskName := \"test_task\"\n\terr := server.RegisterTask(taskName, func() error { return nil })\n\tassert.NoError(t, err)\n\n\ttaskNames := server.GetRegisteredTaskNames()\n\tassert.Equal(t, 1, len(taskNames))\n\tassert.Equal(t, taskName, taskNames[0])\n}\n\nfunc TestNewWorker(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\n\tserver.NewWorker(\"test_worker\", 1)\n\tassert.NoError(t, nil)\n}\n\nfunc TestNewCustomQueueWorker(t *testing.T) {\n\tt.Parallel()\n\n\tserver := getTestServer(t)\n\n\tserver.NewCustomQueueWorker(\"test_customqueueworker\", 1, \"test_queue\")\n\tassert.NoError(t, nil)\n}\n\nfunc getTestServer(t *testing.T) *machinery.Server {\n\treturn machinery.NewServer(&config.Config{}, broker.New(), backend.New(), lock.New())\n}\n"
  },
  {
    "path": "v2/tasks/errors.go",
    "content": "package tasks\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\n// ErrRetryTaskLater ...\ntype ErrRetryTaskLater struct {\n\tname, msg string\n\tretryIn   time.Duration\n}\n\n// RetryIn returns time.Duration from now when task should be retried\nfunc (e ErrRetryTaskLater) RetryIn() time.Duration {\n\treturn e.retryIn\n}\n\n// Error implements the error interface\nfunc (e ErrRetryTaskLater) Error() string {\n\treturn fmt.Sprintf(\"Task error: %s Will retry in: %s\", e.msg, e.retryIn)\n}\n\n// NewErrRetryTaskLater returns new ErrRetryTaskLater instance\nfunc NewErrRetryTaskLater(msg string, retryIn time.Duration) ErrRetryTaskLater {\n\treturn ErrRetryTaskLater{msg: msg, retryIn: retryIn}\n}\n\n// Retriable is interface that retriable errors should implement\ntype Retriable interface {\n\tRetryIn() time.Duration\n}\n"
  },
  {
    "path": "v2/tasks/reflect.go",
    "content": "package tasks\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar (\n\ttypesMap = map[string]reflect.Type{\n\t\t// base types\n\t\t\"bool\":    reflect.TypeOf(true),\n\t\t\"int\":     reflect.TypeOf(int(1)),\n\t\t\"int8\":    reflect.TypeOf(int8(1)),\n\t\t\"int16\":   reflect.TypeOf(int16(1)),\n\t\t\"int32\":   reflect.TypeOf(int32(1)),\n\t\t\"int64\":   reflect.TypeOf(int64(1)),\n\t\t\"uint\":    reflect.TypeOf(uint(1)),\n\t\t\"uint8\":   reflect.TypeOf(uint8(1)),\n\t\t\"uint16\":  reflect.TypeOf(uint16(1)),\n\t\t\"uint32\":  reflect.TypeOf(uint32(1)),\n\t\t\"uint64\":  reflect.TypeOf(uint64(1)),\n\t\t\"float32\": reflect.TypeOf(float32(0.5)),\n\t\t\"float64\": reflect.TypeOf(float64(0.5)),\n\t\t\"string\":  reflect.TypeOf(string(\"\")),\n\t\t// slices\n\t\t\"[]bool\":    reflect.TypeOf(make([]bool, 0)),\n\t\t\"[]int\":     reflect.TypeOf(make([]int, 0)),\n\t\t\"[]int8\":    reflect.TypeOf(make([]int8, 0)),\n\t\t\"[]int16\":   reflect.TypeOf(make([]int16, 0)),\n\t\t\"[]int32\":   reflect.TypeOf(make([]int32, 0)),\n\t\t\"[]int64\":   reflect.TypeOf(make([]int64, 0)),\n\t\t\"[]uint\":    reflect.TypeOf(make([]uint, 0)),\n\t\t\"[]uint8\":   reflect.TypeOf(make([]uint8, 0)),\n\t\t\"[]uint16\":  reflect.TypeOf(make([]uint16, 0)),\n\t\t\"[]uint32\":  reflect.TypeOf(make([]uint32, 0)),\n\t\t\"[]uint64\":  reflect.TypeOf(make([]uint64, 0)),\n\t\t\"[]float32\": reflect.TypeOf(make([]float32, 0)),\n\t\t\"[]float64\": reflect.TypeOf(make([]float64, 0)),\n\t\t\"[]byte\":    reflect.TypeOf(make([]byte, 0)),\n\t\t\"[]string\":  reflect.TypeOf([]string{\"\"}),\n\t}\n\n\tctxType = reflect.TypeOf((*context.Context)(nil)).Elem()\n\n\ttypeConversionError = func(argValue interface{}, argTypeStr string) error {\n\t\treturn fmt.Errorf(\"%v is not %v\", argValue, argTypeStr)\n\t}\n)\n\n// ErrUnsupportedType ...\ntype ErrUnsupportedType struct {\n\tvalueType string\n}\n\n// NewErrUnsupportedType returns new ErrUnsupportedType\nfunc NewErrUnsupportedType(valueType string) ErrUnsupportedType {\n\treturn ErrUnsupportedType{valueType}\n}\n\n// Error method so we implement the error interface\nfunc (e ErrUnsupportedType) Error() string {\n\treturn fmt.Sprintf(\"%v is not one of supported types\", e.valueType)\n}\n\n// ReflectValue converts interface{} to reflect.Value based on string type\nfunc ReflectValue(valueType string, value interface{}) (reflect.Value, error) {\n\tif strings.HasPrefix(valueType, \"[]\") {\n\t\treturn reflectValues(valueType, value)\n\t}\n\n\treturn reflectValue(valueType, value)\n}\n\n// reflectValue converts interface{} to reflect.Value based on string type\n// representing a base type (not a slice)\nfunc reflectValue(valueType string, value interface{}) (reflect.Value, error) {\n\ttheType, ok := typesMap[valueType]\n\tif !ok {\n\t\treturn reflect.Value{}, NewErrUnsupportedType(valueType)\n\t}\n\ttheValue := reflect.New(theType)\n\n\t// Booleans\n\tif theType.String() == \"bool\" {\n\t\tboolValue, err := getBoolValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetBool(boolValue)\n\t\treturn theValue.Elem(), nil\n\t}\n\n\t// Integers\n\tif strings.HasPrefix(theType.String(), \"int\") {\n\t\tintValue, err := getIntValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetInt(intValue)\n\t\treturn theValue.Elem(), err\n\t}\n\n\t// Unsigned integers\n\tif strings.HasPrefix(theType.String(), \"uint\") {\n\t\tuintValue, err := getUintValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetUint(uintValue)\n\t\treturn theValue.Elem(), err\n\t}\n\n\t// Floating point numbers\n\tif strings.HasPrefix(theType.String(), \"float\") {\n\t\tfloatValue, err := getFloatValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetFloat(floatValue)\n\t\treturn theValue.Elem(), err\n\t}\n\n\t// Strings\n\tif theType.String() == \"string\" {\n\t\tstringValue, err := getStringValue(theType.String(), value)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\n\t\ttheValue.Elem().SetString(stringValue)\n\t\treturn theValue.Elem(), nil\n\t}\n\n\treturn reflect.Value{}, NewErrUnsupportedType(valueType)\n}\n\n// reflectValues converts interface{} to reflect.Value based on string type\n// representing a slice of values\nfunc reflectValues(valueType string, value interface{}) (reflect.Value, error) {\n\ttheType, ok := typesMap[valueType]\n\tif !ok {\n\t\treturn reflect.Value{}, NewErrUnsupportedType(valueType)\n\t}\n\n\t// For NULL we return an empty slice\n\tif value == nil {\n\t\treturn reflect.MakeSlice(theType, 0, 0), nil\n\t}\n\n\tvar theValue reflect.Value\n\n\t// Booleans\n\tif theType.String() == \"[]bool\" {\n\t\tbools := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, bools.Len(), bools.Len())\n\t\tfor i := 0; i < bools.Len(); i++ {\n\t\t\tboolValue, err := getBoolValue(strings.Split(theType.String(), \"[]\")[1], bools.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetBool(boolValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\t// Integers\n\tif strings.HasPrefix(theType.String(), \"[]int\") {\n\t\tints := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, ints.Len(), ints.Len())\n\t\tfor i := 0; i < ints.Len(); i++ {\n\t\t\tintValue, err := getIntValue(strings.Split(theType.String(), \"[]\")[1], ints.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetInt(intValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\t// Unsigned integers\n\tif strings.HasPrefix(theType.String(), \"[]uint\") || theType.String() == \"[]byte\" {\n\n\t\t// Decode the base64 string if the value type is []uint8 or it's alias []byte\n\t\t// See: https://golang.org/pkg/encoding/json/#Marshal\n\t\t// > Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string\n\t\tif reflect.TypeOf(value).String() == \"string\" {\n\t\t\toutput, err := base64.StdEncoding.DecodeString(value.(string))\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\t\t\tvalue = output\n\t\t}\n\n\t\tuints := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, uints.Len(), uints.Len())\n\t\tfor i := 0; i < uints.Len(); i++ {\n\t\t\tuintValue, err := getUintValue(strings.Split(theType.String(), \"[]\")[1], uints.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetUint(uintValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\t// Floating point numbers\n\tif strings.HasPrefix(theType.String(), \"[]float\") {\n\t\tfloats := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, floats.Len(), floats.Len())\n\t\tfor i := 0; i < floats.Len(); i++ {\n\t\t\tfloatValue, err := getFloatValue(strings.Split(theType.String(), \"[]\")[1], floats.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetFloat(floatValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\t// Strings\n\tif theType.String() == \"[]string\" {\n\t\tstrs := reflect.ValueOf(value)\n\n\t\ttheValue = reflect.MakeSlice(theType, strs.Len(), strs.Len())\n\t\tfor i := 0; i < strs.Len(); i++ {\n\t\t\tstrValue, err := getStringValue(strings.Split(theType.String(), \"[]\")[1], strs.Index(i).Interface())\n\t\t\tif err != nil {\n\t\t\t\treturn reflect.Value{}, err\n\t\t\t}\n\n\t\t\ttheValue.Index(i).SetString(strValue)\n\t\t}\n\n\t\treturn theValue, nil\n\t}\n\n\treturn reflect.Value{}, NewErrUnsupportedType(valueType)\n}\n\nfunc getBoolValue(theType string, value interface{}) (bool, error) {\n\tb, ok := value.(bool)\n\tif !ok {\n\t\treturn false, typeConversionError(value, typesMap[theType].String())\n\t}\n\n\treturn b, nil\n}\n\nfunc getIntValue(theType string, value interface{}) (int64, error) {\n\t// We use https://golang.org/pkg/encoding/json/#Decoder.UseNumber when unmarshaling signatures.\n\t// This is because JSON only supports 64-bit floating point numbers and we could lose precision\n\t// when converting from float64 to signed integer\n\tif strings.HasPrefix(fmt.Sprintf(\"%T\", value), \"json.Number\") {\n\t\tn, ok := value.(json.Number)\n\t\tif !ok {\n\t\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t\t}\n\n\t\treturn n.Int64()\n\t}\n\n\tn, ok := value.(int64)\n\tif !ok {\n\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t}\n\n\treturn n, nil\n}\n\nfunc getUintValue(theType string, value interface{}) (uint64, error) {\n\t// Losing precision only happens in receiving a JSON number from a language like js,\n\t// and receiving a large uint number from golang or python could cause json.Number.Int64 be turned into a panic.\n\t// So we use strconv.ParseUint to correctly parse a uint value.\n\tif strings.HasPrefix(fmt.Sprintf(\"%T\", value), \"json.Number\") {\n\t\tn, ok := value.(json.Number)\n\t\tif !ok {\n\t\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t\t}\n\n\t\tuintVal, err := strconv.ParseUint(string(n), 10, 64)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\treturn uintVal, nil\n\t}\n\n\tvar n uint64\n\tswitch value := value.(type) {\n\tcase uint64:\n\t\tn = value\n\tcase uint8:\n\t\tn = uint64(value)\n\tdefault:\n\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t}\n\treturn n, nil\n}\n\nfunc getFloatValue(theType string, value interface{}) (float64, error) {\n\t// We use https://golang.org/pkg/encoding/json/#Decoder.UseNumber when unmarshaling signatures.\n\t// This is because JSON only supports 64-bit floating point numbers and we could lose precision\n\tif strings.HasPrefix(fmt.Sprintf(\"%T\", value), \"json.Number\") {\n\t\tn, ok := value.(json.Number)\n\t\tif !ok {\n\t\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t\t}\n\n\t\treturn n.Float64()\n\t}\n\n\tf, ok := value.(float64)\n\tif !ok {\n\t\treturn 0, typeConversionError(value, typesMap[theType].String())\n\t}\n\n\treturn f, nil\n}\n\nfunc getStringValue(theType string, value interface{}) (string, error) {\n\ts, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", typeConversionError(value, typesMap[theType].String())\n\t}\n\n\treturn s, nil\n}\n\n// IsContextType checks to see if the type is a context.Context\nfunc IsContextType(t reflect.Type) bool {\n\treturn t == ctxType\n}\n"
  },
  {
    "path": "v2/tasks/reflect_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n)\n\nvar (\n\treflectValuesTestCases = []struct {\n\t\tname          string\n\t\tvalue         interface{}\n\t\texpectedType  string\n\t\texpectedValue interface{}\n\t}{\n\t\t// basic types\n\t\t{\n\t\t\tname:         \"bool\",\n\t\t\tvalue:        false,\n\t\t\texpectedType: \"bool\",\n\t\t},\n\t\t{\n\t\t\tname:          \"int\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"int\",\n\t\t\texpectedValue: int(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"int8\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"int8\",\n\t\t\texpectedValue: int8(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"int16\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"int16\",\n\t\t\texpectedValue: int16(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"int32\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"int32\",\n\t\t\texpectedValue: int32(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"int64\",\n\t\t\tvalue:         json.Number(\"185135722552891243\"),\n\t\t\texpectedType:  \"int64\",\n\t\t\texpectedValue: int64(185135722552891243),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"uint\",\n\t\t\texpectedValue: uint(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint8\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"uint8\",\n\t\t\texpectedValue: uint8(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint16\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"uint16\",\n\t\t\texpectedValue: uint16(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint32\",\n\t\t\tvalue:         json.Number(\"123\"),\n\t\t\texpectedType:  \"uint32\",\n\t\t\texpectedValue: uint32(123),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint64\",\n\t\t\tvalue:         json.Number(\"185135722552891243\"),\n\t\t\texpectedType:  \"uint64\",\n\t\t\texpectedValue: uint64(185135722552891243),\n\t\t},\n\t\t{\n\t\t\tname:          \"uint64\",\n\t\t\tvalue:         json.Number(\"9223372036854775808\"), // math.MaxInt64 + 1\n\t\t\texpectedType:  \"uint64\",\n\t\t\texpectedValue: uint64(9223372036854775808),\n\t\t},\n\t\t{\n\t\t\tname:          \"float32\",\n\t\t\tvalue:         json.Number(\"0.5\"),\n\t\t\texpectedType:  \"float32\",\n\t\t\texpectedValue: float32(0.5),\n\t\t},\n\t\t{\n\t\t\tname:          \"float64\",\n\t\t\tvalue:         json.Number(\"0.5\"),\n\t\t\texpectedType:  \"float64\",\n\t\t\texpectedValue: float64(0.5),\n\t\t},\n\t\t{\n\t\t\tname:          \"string\",\n\t\t\tvalue:         \"123\",\n\t\t\texpectedType:  \"string\",\n\t\t\texpectedValue: \"123\",\n\t\t},\n\t\t// slices\n\t\t{\n\t\t\tname:          \"[]bool\",\n\t\t\tvalue:         []interface{}{false, true},\n\t\t\texpectedType:  \"[]bool\",\n\t\t\texpectedValue: []bool{false, true},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int\",\n\t\t\texpectedValue: []int{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int8\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int8\",\n\t\t\texpectedValue: []int8{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int16\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int16\",\n\t\t\texpectedValue: []int16{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int32\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int32\",\n\t\t\texpectedValue: []int32{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int64\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]int64\",\n\t\t\texpectedValue: []int64{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint\",\n\t\t\texpectedValue: []uint{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint8\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint8\",\n\t\t\texpectedValue: []uint8{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint16\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint16\",\n\t\t\texpectedValue: []uint16{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint32\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint32\",\n\t\t\texpectedValue: []uint32{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint64\",\n\t\t\tvalue:         []interface{}{json.Number(\"1\"), json.Number(\"2\")},\n\t\t\texpectedType:  \"[]uint64\",\n\t\t\texpectedValue: []uint64{1, 2},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]float32\",\n\t\t\tvalue:         []interface{}{json.Number(\"0.5\"), json.Number(\"1.28\")},\n\t\t\texpectedType:  \"[]float32\",\n\t\t\texpectedValue: []float32{0.5, 1.28},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]float64\",\n\t\t\tvalue:         []interface{}{json.Number(\"0.5\"), json.Number(\"1.28\")},\n\t\t\texpectedType:  \"[]float64\",\n\t\t\texpectedValue: []float64{0.5, 1.28},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]string\",\n\t\t\tvalue:         []interface{}{\"foo\", \"bar\"},\n\t\t\texpectedType:  \"[]string\",\n\t\t\texpectedValue: []string{\"foo\", \"bar\"},\n\t\t},\n\t\t// empty slices from NULL\n\t\t{\n\t\t\tname:          \"[]bool\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]bool\",\n\t\t\texpectedValue: []bool{},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]int64\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]int64\",\n\t\t\texpectedValue: []int64{},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]uint64\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]uint64\",\n\t\t\texpectedValue: []uint64{},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]float64\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]float64\",\n\t\t\texpectedValue: []float64{},\n\t\t},\n\t\t{\n\t\t\tname:          \"[]string\",\n\t\t\tvalue:         nil,\n\t\t\texpectedType:  \"[]string\",\n\t\t\texpectedValue: []string{},\n\t\t},\n\t}\n)\n\nfunc TestReflectValue(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, tc := range reflectValuesTestCases {\n\t\ttc := tc // capture range variable\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tvalue, err := tasks.ReflectValue(tc.name, tc.value)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif value.Type().String() != tc.expectedType {\n\t\t\t\tt.Errorf(\"type is %v, want %s\", value.Type().String(), tc.expectedType)\n\t\t\t}\n\t\t\tif tc.expectedValue != nil {\n\t\t\t\tif !reflect.DeepEqual(value.Interface(), tc.expectedValue) {\n\t\t\t\t\tt.Errorf(\"value is %v, want %v\", value.Interface(), tc.expectedValue)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "v2/tasks/result.go",
    "content": "package tasks\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n)\n\n// TaskResult represents an actual return value of a processed task\ntype TaskResult struct {\n\tType  string      `bson:\"type\"`\n\tValue interface{} `bson:\"value\"`\n}\n\n// ReflectTaskResults ...\nfunc ReflectTaskResults(taskResults []*TaskResult) ([]reflect.Value, error) {\n\tresultValues := make([]reflect.Value, len(taskResults))\n\tfor i, taskResult := range taskResults {\n\t\tresultValue, err := ReflectValue(taskResult.Type, taskResult.Value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresultValues[i] = resultValue\n\t}\n\treturn resultValues, nil\n}\n\n// HumanReadableResults ...\nfunc HumanReadableResults(results []reflect.Value) string {\n\tif len(results) == 1 {\n\t\treturn fmt.Sprintf(\"%v\", results[0].Interface())\n\t}\n\n\treadableResults := make([]string, len(results))\n\tfor i := 0; i < len(results); i++ {\n\t\treadableResults[i] = fmt.Sprintf(\"%v\", results[i].Interface())\n\t}\n\n\treturn fmt.Sprintf(\"[%s]\", strings.Join(readableResults, \", \"))\n}\n"
  },
  {
    "path": "v2/tasks/result_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestReflectTaskResults(t *testing.T) {\n\tt.Parallel()\n\n\ttaskResults := []*tasks.TaskResult{\n\t\t{\n\t\t\tType:  \"[]string\",\n\t\t\tValue: []string{\"f\", \"o\", \"o\"},\n\t\t},\n\t}\n\tresults, err := tasks.ReflectTaskResults(taskResults)\n\tif assert.NoError(t, err) {\n\t\tassert.Equal(t, 1, len(results))\n\t\tassert.Equal(t, 3, results[0].Len())\n\t\tassert.Equal(t, \"f\", results[0].Index(0).String())\n\t\tassert.Equal(t, \"o\", results[0].Index(1).String())\n\t\tassert.Equal(t, \"o\", results[0].Index(2).String())\n\t}\n}\n"
  },
  {
    "path": "v2/tasks/signature.go",
    "content": "package tasks\n\nimport (\n\t\"fmt\"\n\t\"github.com/RichardKnop/machinery/v2/utils\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Arg represents a single argument passed to invocation fo a task\ntype Arg struct {\n\tName  string      `bson:\"name\"`\n\tType  string      `bson:\"type\"`\n\tValue interface{} `bson:\"value\"`\n}\n\n// Headers represents the headers which should be used to direct the task\ntype Headers map[string]interface{}\n\n// Set on Headers implements opentracing.TextMapWriter for trace propagation\nfunc (h Headers) Set(key, val string) {\n\th[key] = val\n}\n\n// ForeachKey on Headers implements opentracing.TextMapReader for trace propagation.\n// It is essentially the same as the opentracing.TextMapReader implementation except\n// for the added casting from interface{} to string.\nfunc (h Headers) ForeachKey(handler func(key, val string) error) error {\n\tfor k, v := range h {\n\t\t// Skip any non string values\n\t\tstringValue, ok := v.(string)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := handler(k, stringValue); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Signature represents a single task invocation\ntype Signature struct {\n\tUUID           string\n\tName           string\n\tRoutingKey     string\n\tETA            *time.Time\n\tGroupUUID      string\n\tGroupTaskCount int\n\tArgs           []Arg\n\tHeaders        Headers\n\tPriority       uint8\n\tImmutable      bool\n\tRetryCount     int\n\tRetryTimeout   int\n\tOnSuccess      []*Signature\n\tOnError        []*Signature\n\tChordCallback  *Signature\n\t//MessageGroupId for Broker, e.g. SQS\n\tBrokerMessageGroupId string\n\t//ReceiptHandle of SQS Message\n\tSQSReceiptHandle string\n\t// StopTaskDeletionOnError used with sqs when we want to send failed messages to dlq,\n\t// and don't want machinery to delete from source queue\n\tStopTaskDeletionOnError bool\n\t// IgnoreWhenTaskNotRegistered auto removes the request when there is no handeler available\n\t// When this is true a task with no handler will be ignored and not placed back in the queue\n\tIgnoreWhenTaskNotRegistered bool\n}\n\n// NewSignature creates a new task signature\nfunc NewSignature(name string, args []Arg) (*Signature, error) {\n\tsignatureID := uuid.New().String()\n\treturn &Signature{\n\t\tUUID: fmt.Sprintf(\"task_%v\", signatureID),\n\t\tName: name,\n\t\tArgs: args,\n\t}, nil\n}\n\nfunc CopySignatures(signatures ...*Signature) []*Signature {\n\tvar sigs = make([]*Signature, len(signatures))\n\tfor index, signature := range signatures {\n\t\tsigs[index] = CopySignature(signature)\n\t}\n\treturn sigs\n}\n\nfunc CopySignature(signature *Signature) *Signature {\n\tvar sig = new(Signature)\n\t_ = utils.DeepCopy(sig, signature)\n\treturn sig\n}\n"
  },
  {
    "path": "v2/tasks/state.go",
    "content": "package tasks\n\nimport \"time\"\n\nconst (\n\t// StatePending - initial state of a task\n\tStatePending = \"PENDING\"\n\t// StateReceived - when task is received by a worker\n\tStateReceived = \"RECEIVED\"\n\t// StateStarted - when the worker starts processing the task\n\tStateStarted = \"STARTED\"\n\t// StateRetry - when failed task has been scheduled for retry\n\tStateRetry = \"RETRY\"\n\t// StateSuccess - when the task is processed successfully\n\tStateSuccess = \"SUCCESS\"\n\t// StateFailure - when processing of the task fails\n\tStateFailure = \"FAILURE\"\n)\n\n// TaskState represents a state of a task\ntype TaskState struct {\n\tTaskUUID  string        `bson:\"_id\"`\n\tTaskName  string        `bson:\"task_name\"`\n\tState     string        `bson:\"state\"`\n\tResults   []*TaskResult `bson:\"results\"`\n\tError     string        `bson:\"error\"`\n\tCreatedAt time.Time     `bson:\"created_at\"`\n\tTTL       int64         `bson:\"ttl,omitempty\"`\n}\n\n// GroupMeta stores useful metadata about tasks within the same group\n// E.g. UUIDs of all tasks which are used in order to check if all tasks\n// completed successfully or not and thus whether to trigger chord callback\ntype GroupMeta struct {\n\tGroupUUID      string    `bson:\"_id\"`\n\tTaskUUIDs      []string  `bson:\"task_uuids\"`\n\tChordTriggered bool      `bson:\"chord_triggered\"`\n\tLock           bool      `bson:\"lock\"`\n\tCreatedAt      time.Time `bson:\"created_at\"`\n\tTTL            int64     `bson:\"ttl,omitempty\"`\n}\n\n// NewPendingTaskState ...\nfunc NewPendingTaskState(signature *Signature) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID:  signature.UUID,\n\t\tTaskName:  signature.Name,\n\t\tState:     StatePending,\n\t\tCreatedAt: time.Now().UTC(),\n\t}\n}\n\n// NewReceivedTaskState ...\nfunc NewReceivedTaskState(signature *Signature) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateReceived,\n\t}\n}\n\n// NewStartedTaskState ...\nfunc NewStartedTaskState(signature *Signature) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateStarted,\n\t}\n}\n\n// NewSuccessTaskState ...\nfunc NewSuccessTaskState(signature *Signature, results []*TaskResult) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateSuccess,\n\t\tResults:  results,\n\t}\n}\n\n// NewFailureTaskState ...\nfunc NewFailureTaskState(signature *Signature, err string) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateFailure,\n\t\tError:    err,\n\t}\n}\n\n// NewRetryTaskState ...\nfunc NewRetryTaskState(signature *Signature) *TaskState {\n\treturn &TaskState{\n\t\tTaskUUID: signature.UUID,\n\t\tState:    StateRetry,\n\t}\n}\n\n// IsCompleted returns true if state is SUCCESS or FAILURE,\n// i.e. the task has finished processing and either succeeded or failed.\nfunc (taskState *TaskState) IsCompleted() bool {\n\treturn taskState.IsSuccess() || taskState.IsFailure()\n}\n\n// IsSuccess returns true if state is SUCCESS\nfunc (taskState *TaskState) IsSuccess() bool {\n\treturn taskState.State == StateSuccess\n}\n\n// IsFailure returns true if state is FAILURE\nfunc (taskState *TaskState) IsFailure() bool {\n\treturn taskState.State == StateFailure\n}\n"
  },
  {
    "path": "v2/tasks/state_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTaskStateIsCompleted(t *testing.T) {\n\tt.Parallel()\n\n\ttaskState := &tasks.TaskState{\n\t\tTaskUUID: \"taskUUID\",\n\t\tState:    tasks.StatePending,\n\t}\n\n\tassert.False(t, taskState.IsCompleted())\n\n\ttaskState.State = tasks.StateReceived\n\tassert.False(t, taskState.IsCompleted())\n\n\ttaskState.State = tasks.StateStarted\n\tassert.False(t, taskState.IsCompleted())\n\n\ttaskState.State = tasks.StateSuccess\n\tassert.True(t, taskState.IsCompleted())\n\n\ttaskState.State = tasks.StateFailure\n\tassert.True(t, taskState.IsCompleted())\n}\n"
  },
  {
    "path": "v2/tasks/task.go",
    "content": "package tasks\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"runtime/debug\"\n\n\topentracing \"github.com/opentracing/opentracing-go\"\n\topentracing_ext \"github.com/opentracing/opentracing-go/ext\"\n\topentracing_log \"github.com/opentracing/opentracing-go/log\"\n\n\t\"github.com/RichardKnop/machinery/v2/log\"\n)\n\n// ErrTaskPanicked ...\nvar ErrTaskPanicked = errors.New(\"Invoking task caused a panic\")\n\n// Task wraps a signature and methods used to reflect task arguments and\n// return values after invoking the task\ntype Task struct {\n\tTaskFunc   reflect.Value\n\tUseContext bool\n\tContext    context.Context\n\tArgs       []reflect.Value\n}\n\ntype signatureCtxType struct{}\n\nvar signatureCtx signatureCtxType\n\n// SignatureFromContext gets the signature from the context\nfunc SignatureFromContext(ctx context.Context) *Signature {\n\tif ctx == nil {\n\t\treturn nil\n\t}\n\n\tv := ctx.Value(signatureCtx)\n\tif v == nil {\n\t\treturn nil\n\t}\n\n\tsignature, _ := v.(*Signature)\n\treturn signature\n}\n\n// NewWithSignature is the same as New but injects the signature\nfunc NewWithSignature(taskFunc interface{}, signature *Signature) (*Task, error) {\n\targs := signature.Args\n\tctx := context.Background()\n\tctx = context.WithValue(ctx, signatureCtx, signature)\n\ttask := &Task{\n\t\tTaskFunc: reflect.ValueOf(taskFunc),\n\t\tContext:  ctx,\n\t}\n\n\ttaskFuncType := reflect.TypeOf(taskFunc)\n\tif taskFuncType.NumIn() > 0 {\n\t\targ0Type := taskFuncType.In(0)\n\t\tif IsContextType(arg0Type) {\n\t\t\ttask.UseContext = true\n\t\t}\n\t}\n\n\tif err := task.ReflectArgs(args); err != nil {\n\t\treturn nil, fmt.Errorf(\"Reflect task args error: %s\", err)\n\t}\n\n\treturn task, nil\n}\n\n// New tries to use reflection to convert the function and arguments\n// into a reflect.Value and prepare it for invocation\nfunc New(taskFunc interface{}, args []Arg) (*Task, error) {\n\ttask := &Task{\n\t\tTaskFunc: reflect.ValueOf(taskFunc),\n\t\tContext:  context.Background(),\n\t}\n\n\ttaskFuncType := reflect.TypeOf(taskFunc)\n\tif taskFuncType.NumIn() > 0 {\n\t\targ0Type := taskFuncType.In(0)\n\t\tif IsContextType(arg0Type) {\n\t\t\ttask.UseContext = true\n\t\t}\n\t}\n\n\tif err := task.ReflectArgs(args); err != nil {\n\t\treturn nil, fmt.Errorf(\"Reflect task args error: %s\", err)\n\t}\n\n\treturn task, nil\n}\n\n// Call attempts to call the task with the supplied arguments.\n//\n// `err` is set in the return value in two cases:\n// 1. The reflected function invocation panics (e.g. due to a mismatched\n//    argument list).\n// 2. The task func itself returns a non-nil error.\nfunc (t *Task) Call() (taskResults []*TaskResult, err error) {\n\t// retrieve the span from the task's context and finish it as soon as this function returns\n\tif span := opentracing.SpanFromContext(t.Context); span != nil {\n\t\tdefer span.Finish()\n\t}\n\n\tdefer func() {\n\t\t// Recover from panic and set err.\n\t\tif e := recover(); e != nil {\n\t\t\tswitch e := e.(type) {\n\t\t\tdefault:\n\t\t\t\terr = ErrTaskPanicked\n\t\t\tcase error:\n\t\t\t\terr = e\n\t\t\tcase string:\n\t\t\t\terr = errors.New(e)\n\t\t\t}\n\n\t\t\t// mark the span as failed and dump the error and stack trace to the span\n\t\t\tif span := opentracing.SpanFromContext(t.Context); span != nil {\n\t\t\t\topentracing_ext.Error.Set(span, true)\n\t\t\t\tspan.LogFields(\n\t\t\t\t\topentracing_log.Error(err),\n\t\t\t\t\topentracing_log.Object(\"stack\", string(debug.Stack())),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Print stack trace\n\t\t\tlog.ERROR.Printf(\"%s\", debug.Stack())\n\t\t}\n\t}()\n\n\targs := t.Args\n\n\tif t.UseContext {\n\t\tctxValue := reflect.ValueOf(t.Context)\n\t\targs = append([]reflect.Value{ctxValue}, args...)\n\t}\n\n\t// Invoke the task\n\tresults := t.TaskFunc.Call(args)\n\n\t// Task must return at least a value\n\tif len(results) == 0 {\n\t\treturn nil, ErrTaskReturnsNoValue\n\t}\n\n\t// Last returned value\n\tlastResult := results[len(results)-1]\n\n\t// If the last returned value is not nil, it has to be of error type, if that\n\t// is not the case, return error message, otherwise propagate the task error\n\t// to the caller\n\tif !lastResult.IsNil() {\n\t\t// If the result implements Retriable interface, return instance of Retriable\n\t\tretriableErrorInterface := reflect.TypeOf((*Retriable)(nil)).Elem()\n\t\tif lastResult.Type().Implements(retriableErrorInterface) {\n\t\t\treturn nil, lastResult.Interface().(ErrRetryTaskLater)\n\t\t}\n\n\t\t// Otherwise, check that the result implements the standard error interface,\n\t\t// if not, return ErrLastReturnValueMustBeError error\n\t\terrorInterface := reflect.TypeOf((*error)(nil)).Elem()\n\t\tif !lastResult.Type().Implements(errorInterface) {\n\t\t\treturn nil, ErrLastReturnValueMustBeError\n\t\t}\n\n\t\t// Return the standard error\n\t\treturn nil, lastResult.Interface().(error)\n\t}\n\n\t// Convert reflect values to task results\n\ttaskResults = make([]*TaskResult, len(results)-1)\n\tfor i := 0; i < len(results)-1; i++ {\n\t\tval := results[i].Interface()\n\t\ttypeStr := reflect.TypeOf(val).String()\n\t\ttaskResults[i] = &TaskResult{\n\t\t\tType:  typeStr,\n\t\t\tValue: val,\n\t\t}\n\t}\n\n\treturn taskResults, err\n}\n\n// ReflectArgs converts []TaskArg to []reflect.Value\nfunc (t *Task) ReflectArgs(args []Arg) error {\n\targValues := make([]reflect.Value, len(args))\n\n\tfor i, arg := range args {\n\t\targValue, err := ReflectValue(arg.Type, arg.Value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\targValues[i] = argValue\n\t}\n\n\tt.Args = argValues\n\treturn nil\n}\n"
  },
  {
    "path": "v2/tasks/task_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTaskCallErrorTest(t *testing.T) {\n\tt.Parallel()\n\n\t// Create test task that returns tasks.ErrRetryTaskLater error\n\tretriable := func() error { return tasks.NewErrRetryTaskLater(\"some error\", 4*time.Hour) }\n\n\ttask, err := tasks.New(retriable, []tasks.Arg{})\n\tassert.NoError(t, err)\n\n\t// Invoke TryCall and validate that returned error can be cast to tasks.ErrRetryTaskLater\n\tresults, err := task.Call()\n\tassert.Nil(t, results)\n\tassert.NotNil(t, err)\n\t_, ok := interface{}(err).(tasks.ErrRetryTaskLater)\n\tassert.True(t, ok, \"Error should be castable to tasks.ErrRetryTaskLater\")\n\n\t// Create test task that returns a standard error\n\tstandard := func() error { return errors.New(\"some error\") }\n\n\ttask, err = tasks.New(standard, []tasks.Arg{})\n\tassert.NoError(t, err)\n\n\t// Invoke TryCall and validate that returned error is standard\n\tresults, err = task.Call()\n\tassert.Nil(t, results)\n\tassert.NotNil(t, err)\n\tassert.Equal(t, \"some error\", err.Error())\n}\n\nfunc TestTaskReflectArgs(t *testing.T) {\n\tt.Parallel()\n\n\ttask := new(tasks.Task)\n\targs := []tasks.Arg{\n\t\t{\n\t\t\tType:  \"[]int64\",\n\t\t\tValue: []int64{1, 2},\n\t\t},\n\t}\n\n\terr := task.ReflectArgs(args)\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(task.Args))\n\tassert.Equal(t, \"[]int64\", task.Args[0].Type().String())\n}\n\nfunc TestTaskCallInvalidArgRobustnessError(t *testing.T) {\n\tt.Parallel()\n\n\t// Create a test task function\n\tf := func(x int) error { return nil }\n\n\t// Construct an invalid argument list and reflect it\n\targs := []tasks.Arg{\n\t\t{Type: \"bool\", Value: true},\n\t}\n\n\ttask, err := tasks.New(f, args)\n\tassert.NoError(t, err)\n\n\t// Invoke TryCall and validate error handling\n\tresults, err := task.Call()\n\tassert.Equal(t, \"reflect: Call using bool as type int\", err.Error())\n\tassert.Nil(t, results)\n}\n\nfunc TestTaskCallInterfaceValuedResult(t *testing.T) {\n\tt.Parallel()\n\n\t// Create a test task function\n\tf := func() (interface{}, error) { return math.Pi, nil }\n\n\ttask, err := tasks.New(f, []tasks.Arg{})\n\tassert.NoError(t, err)\n\n\ttaskResults, err := task.Call()\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"float64\", taskResults[0].Type)\n\tassert.Equal(t, math.Pi, taskResults[0].Value)\n}\n\nfunc TestTaskCallWithContext(t *testing.T) {\n\tt.Parallel()\n\n\tf := func(c context.Context) (interface{}, error) {\n\t\tassert.NotNil(t, c)\n\t\tassert.Nil(t, tasks.SignatureFromContext(c))\n\t\treturn math.Pi, nil\n\t}\n\ttask, err := tasks.New(f, []tasks.Arg{})\n\tassert.NoError(t, err)\n\ttaskResults, err := task.Call()\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"float64\", taskResults[0].Type)\n\tassert.Equal(t, math.Pi, taskResults[0].Value)\n}\n\nfunc TestTaskCallWithSignatureInContext(t *testing.T) {\n\tt.Parallel()\n\n\tf := func(c context.Context) (interface{}, error) {\n\t\tassert.NotNil(t, c)\n\t\tsignature := tasks.SignatureFromContext(c)\n\t\tassert.NotNil(t, signature)\n\t\tassert.Equal(t, \"foo\", signature.Name)\n\t\treturn math.Pi, nil\n\t}\n\tsignature, err := tasks.NewSignature(\"foo\", []tasks.Arg{})\n\tassert.NoError(t, err)\n\ttask, err := tasks.NewWithSignature(f, signature)\n\tassert.NoError(t, err)\n\ttaskResults, err := task.Call()\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"float64\", taskResults[0].Type)\n\tassert.Equal(t, math.Pi, taskResults[0].Value)\n}\n"
  },
  {
    "path": "v2/tasks/validate.go",
    "content": "package tasks\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n)\n\nvar (\n\t// ErrTaskMustBeFunc ...\n\tErrTaskMustBeFunc = errors.New(\"Task must be a func type\")\n\t// ErrTaskReturnsNoValue ...\n\tErrTaskReturnsNoValue = errors.New(\"Task must return at least a single value\")\n\t// ErrLastReturnValueMustBeError ..\n\tErrLastReturnValueMustBeError = errors.New(\"Last return value of a task must be error\")\n)\n\n// ValidateTask validates task function using reflection and makes sure\n// it has a proper signature. Functions used as tasks must return at least a\n// single value and the last return type must be error\nfunc ValidateTask(task interface{}) error {\n\tv := reflect.ValueOf(task)\n\tt := v.Type()\n\n\t// Task must be a function\n\tif t.Kind() != reflect.Func {\n\t\treturn ErrTaskMustBeFunc\n\t}\n\n\t// Task must return at least a single value\n\tif t.NumOut() < 1 {\n\t\treturn ErrTaskReturnsNoValue\n\t}\n\n\t// Last return value must be error\n\tlastReturnType := t.Out(t.NumOut() - 1)\n\terrorInterface := reflect.TypeOf((*error)(nil)).Elem()\n\tif !lastReturnType.Implements(errorInterface) {\n\t\treturn ErrLastReturnValueMustBeError\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "v2/tasks/validate_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestValidateTask(t *testing.T) {\n\tt.Parallel()\n\n\ttype someStruct struct{}\n\tvar (\n\t\ttaskOfWrongType                   = new(someStruct)\n\t\ttaskWithoutReturnValue            = func() {}\n\t\ttaskWithoutErrorAsLastReturnValue = func() int { return 0 }\n\t\tvalidTask                         = func(arg string) error { return nil }\n\t)\n\n\terr := tasks.ValidateTask(taskOfWrongType)\n\tassert.Equal(t, tasks.ErrTaskMustBeFunc, err)\n\n\terr = tasks.ValidateTask(taskWithoutReturnValue)\n\tassert.Equal(t, tasks.ErrTaskReturnsNoValue, err)\n\n\terr = tasks.ValidateTask(taskWithoutErrorAsLastReturnValue)\n\tassert.Equal(t, tasks.ErrLastReturnValueMustBeError, err)\n\n\terr = tasks.ValidateTask(validTask)\n\tassert.NoError(t, err)\n}\n"
  },
  {
    "path": "v2/tasks/workflow.go",
    "content": "package tasks\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Chain creates a chain of tasks to be executed one after another\ntype Chain struct {\n\tTasks []*Signature\n}\n\n// Group creates a set of tasks to be executed in parallel\ntype Group struct {\n\tGroupUUID string\n\tTasks     []*Signature\n}\n\n// Chord adds an optional callback to the group to be executed\n// after all tasks in the group finished\ntype Chord struct {\n\tGroup    *Group\n\tCallback *Signature\n}\n\n// GetUUIDs returns slice of task UUIDS\nfunc (group *Group) GetUUIDs() []string {\n\ttaskUUIDs := make([]string, len(group.Tasks))\n\tfor i, signature := range group.Tasks {\n\t\ttaskUUIDs[i] = signature.UUID\n\t}\n\treturn taskUUIDs\n}\n\n// NewChain creates a new chain of tasks to be processed one by one, passing\n// results unless task signatures are set to be immutable\nfunc NewChain(signatures ...*Signature) (*Chain, error) {\n\t// Auto generate task UUIDs if needed\n\tfor _, signature := range signatures {\n\t\tif signature.UUID == \"\" {\n\t\t\tsignatureID := uuid.New().String()\n\t\t\tsignature.UUID = fmt.Sprintf(\"task_%v\", signatureID)\n\t\t}\n\t}\n\n\tfor i := len(signatures) - 1; i > 0; i-- {\n\t\tif i > 0 {\n\t\t\tsignatures[i-1].OnSuccess = []*Signature{signatures[i]}\n\t\t}\n\t}\n\n\tchain := &Chain{Tasks: signatures}\n\n\treturn chain, nil\n}\n\n// NewGroup creates a new group of tasks to be processed in parallel\nfunc NewGroup(signatures ...*Signature) (*Group, error) {\n\t// Generate a group UUID\n\tgroupUUID := uuid.New().String()\n\tgroupID := fmt.Sprintf(\"group_%v\", groupUUID)\n\n\t// Auto generate task UUIDs if needed, group tasks by common group UUID\n\tfor _, signature := range signatures {\n\t\tif signature.UUID == \"\" {\n\t\t\tsignatureID := uuid.New().String()\n\t\t\tsignature.UUID = fmt.Sprintf(\"task_%v\", signatureID)\n\t\t}\n\t\tsignature.GroupUUID = groupID\n\t\tsignature.GroupTaskCount = len(signatures)\n\t}\n\n\treturn &Group{\n\t\tGroupUUID: groupID,\n\t\tTasks:     signatures,\n\t}, nil\n}\n\n// NewChord creates a new chord (a group of tasks with a single callback\n// to be executed after all tasks in the group has completed)\nfunc NewChord(group *Group, callback *Signature) (*Chord, error) {\n\tif callback.UUID == \"\" {\n\t\t// Generate a UUID for the chord callback\n\t\tcallbackUUID := uuid.New().String()\n\t\tcallback.UUID = fmt.Sprintf(\"chord_%v\", callbackUUID)\n\t}\n\n\t// Add a chord callback to all tasks\n\tfor _, signature := range group.Tasks {\n\t\tsignature.ChordCallback = callback\n\t}\n\n\treturn &Chord{Group: group, Callback: callback}, nil\n}\n"
  },
  {
    "path": "v2/tasks/workflow_test.go",
    "content": "package tasks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewChain(t *testing.T) {\n\tt.Parallel()\n\n\ttask1 := tasks.Signature{\n\t\tName: \"foo\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(1),\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(1),\n\t\t\t},\n\t\t},\n\t}\n\n\ttask2 := tasks.Signature{\n\t\tName: \"bar\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(5),\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(6),\n\t\t\t},\n\t\t},\n\t}\n\n\ttask3 := tasks.Signature{\n\t\tName: \"qux\",\n\t\tArgs: []tasks.Arg{\n\t\t\t{\n\t\t\t\tType:  \"float64\",\n\t\t\t\tValue: interface{}(4),\n\t\t\t},\n\t\t},\n\t}\n\n\tchain, err := tasks.NewChain(&task1, &task2, &task3)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfirstTask := chain.Tasks[0]\n\n\tassert.Equal(t, \"foo\", firstTask.Name)\n\tassert.Equal(t, \"bar\", firstTask.OnSuccess[0].Name)\n\tassert.Equal(t, \"qux\", firstTask.OnSuccess[0].OnSuccess[0].Name)\n}\n"
  },
  {
    "path": "v2/tracing/tracing.go",
    "content": "package tracing\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\n\topentracing \"github.com/opentracing/opentracing-go\"\n\topentracing_ext \"github.com/opentracing/opentracing-go/ext\"\n\topentracing_log \"github.com/opentracing/opentracing-go/log\"\n)\n\n// opentracing tags\nvar (\n\tMachineryTag     = opentracing.Tag{Key: string(opentracing_ext.Component), Value: \"machinery\"}\n\tWorkflowGroupTag = opentracing.Tag{Key: \"machinery.workflow\", Value: \"group\"}\n\tWorkflowChordTag = opentracing.Tag{Key: \"machinery.workflow\", Value: \"chord\"}\n\tWorkflowChainTag = opentracing.Tag{Key: \"machinery.workflow\", Value: \"chain\"}\n)\n\n// StartSpanFromHeaders will extract a span from the signature headers\n// and start a new span with the given operation name.\nfunc StartSpanFromHeaders(headers tasks.Headers, operationName string) opentracing.Span {\n\t// Try to extract the span context from the carrier.\n\tspanContext, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, headers)\n\n\t// Create a new span from the span context if found or start a new trace with the function name.\n\t// For clarity add the machinery component tag.\n\tspan := opentracing.StartSpan(\n\t\toperationName,\n\t\tConsumerOption(spanContext),\n\t\tMachineryTag,\n\t)\n\n\t// Log any error but don't fail\n\tif err != nil {\n\t\tspan.LogFields(opentracing_log.Error(err))\n\t}\n\n\treturn span\n}\n\n// HeadersWithSpan will inject a span into the signature headers\nfunc HeadersWithSpan(headers tasks.Headers, span opentracing.Span) tasks.Headers {\n\t// check if the headers aren't nil\n\tif headers == nil {\n\t\theaders = make(tasks.Headers)\n\t}\n\n\tif err := opentracing.GlobalTracer().Inject(span.Context(), opentracing.TextMap, headers); err != nil {\n\t\tspan.LogFields(opentracing_log.Error(err))\n\t}\n\n\treturn headers\n}\n\ntype consumerOption struct {\n\tproducerContext opentracing.SpanContext\n}\n\nfunc (c consumerOption) Apply(o *opentracing.StartSpanOptions) {\n\tif c.producerContext != nil {\n\t\topentracing.FollowsFrom(c.producerContext).Apply(o)\n\t}\n\topentracing_ext.SpanKindConsumer.Apply(o)\n}\n\n// ConsumerOption ...\nfunc ConsumerOption(producer opentracing.SpanContext) opentracing.StartSpanOption {\n\treturn consumerOption{producer}\n}\n\ntype producerOption struct{}\n\nfunc (p producerOption) Apply(o *opentracing.StartSpanOptions) {\n\topentracing_ext.SpanKindProducer.Apply(o)\n}\n\n// ProducerOption ...\nfunc ProducerOption() opentracing.StartSpanOption {\n\treturn producerOption{}\n}\n\n// AnnotateSpanWithSignatureInfo ...\nfunc AnnotateSpanWithSignatureInfo(span opentracing.Span, signature *tasks.Signature) {\n\t// tag the span with some info about the signature\n\tspan.SetTag(\"signature.name\", signature.Name)\n\tspan.SetTag(\"signature.uuid\", signature.UUID)\n\n\tif signature.GroupUUID != \"\" {\n\t\tspan.SetTag(\"signature.group.uuid\", signature.GroupUUID)\n\t}\n\n\tif signature.ChordCallback != nil {\n\t\tspan.SetTag(\"signature.chord.callback.uuid\", signature.ChordCallback.UUID)\n\t\tspan.SetTag(\"signature.chord.callback.name\", signature.ChordCallback.Name)\n\t}\n}\n\n// AnnotateSpanWithChainInfo ...\nfunc AnnotateSpanWithChainInfo(span opentracing.Span, chain *tasks.Chain) {\n\t// tag the span with some info about the chain\n\tspan.SetTag(\"chain.tasks.length\", len(chain.Tasks))\n\n\t// inject the tracing span into the tasks signature headers\n\tfor _, signature := range chain.Tasks {\n\t\tsignature.Headers = HeadersWithSpan(signature.Headers, span)\n\t}\n}\n\n// AnnotateSpanWithGroupInfo ...\nfunc AnnotateSpanWithGroupInfo(span opentracing.Span, group *tasks.Group, sendConcurrency int) {\n\t// tag the span with some info about the group\n\tspan.SetTag(\"group.uuid\", group.GroupUUID)\n\tspan.SetTag(\"group.tasks.length\", len(group.Tasks))\n\tspan.SetTag(\"group.concurrency\", sendConcurrency)\n\n\t// encode the task uuids to json, if that fails just dump it in\n\tif taskUUIDs, err := json.Marshal(group.GetUUIDs()); err == nil {\n\t\tspan.SetTag(\"group.tasks\", string(taskUUIDs))\n\t} else {\n\t\tspan.SetTag(\"group.tasks\", group.GetUUIDs())\n\t}\n\n\t// inject the tracing span into the tasks signature headers\n\tfor _, signature := range group.Tasks {\n\t\tsignature.Headers = HeadersWithSpan(signature.Headers, span)\n\t}\n}\n\n// AnnotateSpanWithChordInfo ...\nfunc AnnotateSpanWithChordInfo(span opentracing.Span, chord *tasks.Chord, sendConcurrency int) {\n\t// tag the span with chord specific info\n\tspan.SetTag(\"chord.callback.uuid\", chord.Callback.UUID)\n\n\t// inject the tracing span into the callback signature\n\tchord.Callback.Headers = HeadersWithSpan(chord.Callback.Headers, span)\n\n\t// tag the span for the group part of the chord\n\tAnnotateSpanWithGroupInfo(span, chord.Group, sendConcurrency)\n}\n"
  },
  {
    "path": "v2/utils/deepcopy.go",
    "content": "package utils\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n)\n\nvar (\n\tErrNoMatchType     = errors.New(\"no match type\")\n\tErrNoPointer       = errors.New(\"must be interface\")\n\tErrInvalidArgument = errors.New(\"invalid arguments\")\n)\n\nfunc deepCopy(dst, src reflect.Value) {\n\tswitch src.Kind() {\n\tcase reflect.Interface:\n\t\tvalue := src.Elem()\n\t\tif !value.IsValid() {\n\t\t\treturn\n\t\t}\n\t\tnewValue := reflect.New(value.Type()).Elem()\n\t\tdeepCopy(newValue, value)\n\t\tdst.Set(newValue)\n\tcase reflect.Ptr:\n\t\tvalue := src.Elem()\n\t\tif !value.IsValid() {\n\t\t\treturn\n\t\t}\n\t\tdst.Set(reflect.New(value.Type()))\n\t\tdeepCopy(dst.Elem(), value)\n\tcase reflect.Map:\n\t\tdst.Set(reflect.MakeMap(src.Type()))\n\t\tkeys := src.MapKeys()\n\t\tfor _, key := range keys {\n\t\t\tvalue := src.MapIndex(key)\n\t\t\tnewValue := reflect.New(value.Type()).Elem()\n\t\t\tdeepCopy(newValue, value)\n\t\t\tdst.SetMapIndex(key, newValue)\n\t\t}\n\tcase reflect.Slice:\n\t\tdst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))\n\t\tfor i := 0; i < src.Len(); i++ {\n\t\t\tdeepCopy(dst.Index(i), src.Index(i))\n\t\t}\n\tcase reflect.Struct:\n\t\ttypeSrc := src.Type()\n\t\tfor i := 0; i < src.NumField(); i++ {\n\t\t\tvalue := src.Field(i)\n\t\t\ttag := typeSrc.Field(i).Tag\n\t\t\tif value.CanSet() && tag.Get(\"deepcopy\") != \"-\" {\n\t\t\t\tdeepCopy(dst.Field(i), value)\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tdst.Set(src)\n\t}\n}\n\nfunc DeepCopy(dst, src interface{}) error {\n\ttypeDst := reflect.TypeOf(dst)\n\ttypeSrc := reflect.TypeOf(src)\n\tif typeDst != typeSrc {\n\t\treturn ErrNoMatchType\n\t}\n\tif typeSrc.Kind() != reflect.Ptr {\n\t\treturn ErrNoPointer\n\t}\n\n\tvalueDst := reflect.ValueOf(dst).Elem()\n\tvalueSrc := reflect.ValueOf(src).Elem()\n\tif !valueDst.IsValid() || !valueSrc.IsValid() {\n\t\treturn ErrInvalidArgument\n\t}\n\n\tdeepCopy(valueDst, valueSrc)\n\treturn nil\n}\n\nfunc DeepClone(v interface{}) interface{} {\n\tdst := reflect.New(reflect.TypeOf(v)).Elem()\n\tdeepCopy(dst, reflect.ValueOf(v))\n\treturn dst.Interface()\n}\n"
  },
  {
    "path": "v2/utils/deepcopy_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDeepCopy(t *testing.T) {\n\tt.Parallel()\n\n\ttype s struct {\n\t\tA float64\n\t\tB int\n\t\tC []int\n\t\tD *int\n\t\tE map[string]int\n\t}\n\tvar d = 3\n\tvar dst = new(s)\n\tvar src = s{1.0, 1, []int{1, 2, 3}, &d, map[string]int{\"a\": 1}}\n\n\terr := DeepCopy(dst, &src)\n\tsrc.A = 2\n\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1.0, dst.A)\n\tassert.Equal(t, 1, dst.B)\n\tassert.Equal(t, []int{1, 2, 3}, dst.C)\n\tassert.Equal(t, &d, dst.D)\n\tassert.Equal(t, map[string]int{\"a\": 1}, dst.E)\n}\n"
  },
  {
    "path": "v2/utils/utils.go",
    "content": "package utils\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\tLockKeyPrefix = \"machinery_lock_\"\n)\n\nfunc GetLockName(name, spec string) string {\n\treturn LockKeyPrefix + filepath.Base(os.Args[0]) + name + spec\n}\n"
  },
  {
    "path": "v2/utils/utils_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetLockName(t *testing.T) {\n\tt.Parallel()\n\n\tlockName := GetLockName(\"test\", \"*/3 * * *\")\n\tassert.Equal(t, \"machinery_lock_utils.testtest*/3 * * *\", lockName)\n}\n"
  },
  {
    "path": "v2/utils/uuid.go",
    "content": "package utils\n\nimport (\n\t\"github.com/google/uuid\"\n\t\"strings\"\n)\n\nfunc GetPureUUID() string {\n\tuid, _ := uuid.NewUUID()\n\treturn strings.Replace(uid.String(), \"-\", \"\", -1)\n}\n"
  },
  {
    "path": "v2/utils/uuid_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetPureUUID(t *testing.T) {\n\tt.Parallel()\n\n\tassert.Len(t, GetPureUUID(), 32)\n}\n"
  },
  {
    "path": "v2/wait-for-it.sh",
    "content": "#!/usr/bin/env bash\n# https://github.com/vishnubob/wait-for-it/blob/master/wait-for-it.sh\n#   Use this script to test if a given TCP host/port are available\n\ncmdname=$(basename $0)\n\nechoerr() { if [[ $QUIET -ne 1 ]]; then echo \"$@\" 1>&2; fi }\n\nusage()\n{\n    cat << USAGE >&2\nUsage:\n    $cmdname host:port [-s] [-t timeout] [-- command args]\n    -h HOST | --host=HOST       Host or IP under test\n    -p PORT | --port=PORT       TCP port under test\n                                Alternatively, you specify the host and port as host:port\n    -s | --strict               Only execute subcommand if the test succeeds\n    -q | --quiet                Don't output any status messages\n    -t TIMEOUT | --timeout=TIMEOUT\n                                Timeout in seconds, zero for no timeout\n    -- COMMAND ARGS             Execute command with args after the test finishes\nUSAGE\n    exit 1\n}\n\nwait_for()\n{\n    if [[ $TIMEOUT -gt 0 ]]; then\n        echoerr \"$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT\"\n    else\n        echoerr \"$cmdname: waiting for $HOST:$PORT without a timeout\"\n    fi\n    start_ts=$(date +%s)\n    while :\n    do\n        if [[ $ISBUSY -eq 1 ]]; then\n            nc -z $HOST $PORT\n            result=$?\n        else\n            (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1\n            result=$?\n        fi\n        if [[ $result -eq 0 ]]; then\n            end_ts=$(date +%s)\n            echoerr \"$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds\"\n            break\n        fi\n        sleep 1\n    done\n    return $result\n}\n\nwait_for_wrapper()\n{\n    # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692\n    if [[ $QUIET -eq 1 ]]; then\n        timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &\n    else\n        timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &\n    fi\n    PID=$!\n    trap \"kill -INT -$PID\" INT\n    wait $PID\n    RESULT=$?\n    if [[ $RESULT -ne 0 ]]; then\n        echoerr \"$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT\"\n    fi\n    return $RESULT\n}\n\n# process arguments\nwhile [[ $# -gt 0 ]]\ndo\n    case \"$1\" in\n        *:* )\n        hostport=(${1//:/ })\n        HOST=${hostport[0]}\n        PORT=${hostport[1]}\n        shift 1\n        ;;\n        --child)\n        CHILD=1\n        shift 1\n        ;;\n        -q | --quiet)\n        QUIET=1\n        shift 1\n        ;;\n        -s | --strict)\n        STRICT=1\n        shift 1\n        ;;\n        -h)\n        HOST=\"$2\"\n        if [[ $HOST == \"\" ]]; then break; fi\n        shift 2\n        ;;\n        --host=*)\n        HOST=\"${1#*=}\"\n        shift 1\n        ;;\n        -p)\n        PORT=\"$2\"\n        if [[ $PORT == \"\" ]]; then break; fi\n        shift 2\n        ;;\n        --port=*)\n        PORT=\"${1#*=}\"\n        shift 1\n        ;;\n        -t)\n        TIMEOUT=\"$2\"\n        if [[ $TIMEOUT == \"\" ]]; then break; fi\n        shift 2\n        ;;\n        --timeout=*)\n        TIMEOUT=\"${1#*=}\"\n        shift 1\n        ;;\n        --)\n        shift\n        CLI=(\"$@\")\n        break\n        ;;\n        --help)\n        usage\n        ;;\n        *)\n        echoerr \"Unknown argument: $1\"\n        usage\n        ;;\n    esac\ndone\n\nif [[ \"$HOST\" == \"\" || \"$PORT\" == \"\" ]]; then\n    echoerr \"Error: you need to provide a host and port to test.\"\n    usage\nfi\n\nTIMEOUT=${TIMEOUT:-15}\nSTRICT=${STRICT:-0}\nCHILD=${CHILD:-0}\nQUIET=${QUIET:-0}\n\n# check to see if timeout is from busybox?\nTIMEOUT_PATH=$(realpath $(which timeout))\nif [[ $TIMEOUT_PATH =~ \"busybox\" ]]; then\n        ISBUSY=1\n        BUSYTIMEFLAG=\"-t\"\nelse\n        ISBUSY=0\n        BUSYTIMEFLAG=\"\"\nfi\n\nif [[ $CHILD -gt 0 ]]; then\n    wait_for\n    RESULT=$?\n    exit $RESULT\nelse\n    if [[ $TIMEOUT -gt 0 ]]; then\n        wait_for_wrapper\n        RESULT=$?\n    else\n        wait_for\n        RESULT=$?\n    fi\nfi\n\nif [[ $CLI != \"\" ]]; then\n    if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then\n        echoerr \"$cmdname: strict mode, refusing to execute subprocess\"\n        exit $RESULT\n    fi\n    exec \"${CLI[@]}\"\nelse\n    exit $RESULT\nfi\n"
  },
  {
    "path": "v2/worker.go",
    "content": "package machinery\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/opentracing/opentracing-go\"\n\t\n\t\"github.com/RichardKnop/machinery/v2/backends/amqp\"\n\t\"github.com/RichardKnop/machinery/v2/brokers/errs\"\n\t\"github.com/RichardKnop/machinery/v2/log\"\n\t\"github.com/RichardKnop/machinery/v2/retry\"\n\t\"github.com/RichardKnop/machinery/v2/tasks\"\n\t\"github.com/RichardKnop/machinery/v2/tracing\"\n)\n\n// Worker represents a single worker process\ntype Worker struct {\n\tserver            *Server\n\tConsumerTag       string\n\tConcurrency       int\n\tQueue             string\n\terrorHandler      func(err error)\n\tpreTaskHandler    func(*tasks.Signature)\n\tpostTaskHandler   func(*tasks.Signature)\n\tpreConsumeHandler func(*Worker) bool\n}\n\nvar (\n\t// ErrWorkerQuitGracefully is return when worker quit gracefully\n\tErrWorkerQuitGracefully = errors.New(\"Worker quit gracefully\")\n\t// ErrWorkerQuitGracefully is return when worker quit abruptly\n\tErrWorkerQuitAbruptly = errors.New(\"Worker quit abruptly\")\n)\n\n// Launch starts a new worker process. The worker subscribes\n// to the default queue and processes incoming registered tasks\nfunc (worker *Worker) Launch() error {\n\terrorsChan := make(chan error)\n\n\tworker.LaunchAsync(errorsChan)\n\n\treturn <-errorsChan\n}\n\n// LaunchAsync is a non blocking version of Launch\nfunc (worker *Worker) LaunchAsync(errorsChan chan<- error) {\n\tcnf := worker.server.GetConfig()\n\tbroker := worker.server.GetBroker()\n\n\t// Log some useful information about worker configuration\n\tlog.INFO.Printf(\"Launching a worker with the following settings:\")\n\tlog.INFO.Printf(\"- Broker: %s\", RedactURL(cnf.Broker))\n\tif worker.Queue == \"\" {\n\t\tlog.INFO.Printf(\"- DefaultQueue: %s\", cnf.DefaultQueue)\n\t} else {\n\t\tlog.INFO.Printf(\"- CustomQueue: %s\", worker.Queue)\n\t}\n\tlog.INFO.Printf(\"- ResultBackend: %s\", RedactURL(cnf.ResultBackend))\n\tif cnf.AMQP != nil {\n\t\tlog.INFO.Printf(\"- AMQP: %s\", cnf.AMQP.Exchange)\n\t\tlog.INFO.Printf(\"  - Exchange: %s\", cnf.AMQP.Exchange)\n\t\tlog.INFO.Printf(\"  - ExchangeType: %s\", cnf.AMQP.ExchangeType)\n\t\tlog.INFO.Printf(\"  - BindingKey: %s\", cnf.AMQP.BindingKey)\n\t\tlog.INFO.Printf(\"  - PrefetchCount: %d\", cnf.AMQP.PrefetchCount)\n\t}\n\n\tvar signalWG sync.WaitGroup\n\t// Goroutine to start broker consumption and handle retries when broker connection dies\n\tgo func() {\n\t\tfor {\n\t\t\tretry, err := broker.StartConsuming(worker.ConsumerTag, worker.Concurrency, worker)\n\n\t\t\tif retry {\n\t\t\t\tif worker.errorHandler != nil {\n\t\t\t\t\tworker.errorHandler(err)\n\t\t\t\t} else {\n\t\t\t\t\tlog.WARNING.Printf(\"Broker failed with error: %s\", err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsignalWG.Wait()\n\t\t\t\terrorsChan <- err // stop the goroutine\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tif !cnf.NoUnixSignals {\n\t\tsig := make(chan os.Signal, 1)\n\t\tsignal.Notify(sig, os.Interrupt, syscall.SIGTERM)\n\t\tvar signalsReceived uint\n\n\t\t// Goroutine Handle SIGINT and SIGTERM signals\n\t\tgo func() {\n\t\t\tfor s := range sig {\n\t\t\t\tlog.WARNING.Printf(\"Signal received: %v\", s)\n\t\t\t\tsignalsReceived++\n\n\t\t\t\tif signalsReceived < 2 {\n\t\t\t\t\t// After first Ctrl+C start quitting the worker gracefully\n\t\t\t\t\tlog.WARNING.Print(\"Waiting for running tasks to finish before shutting down\")\n\t\t\t\t\tsignalWG.Add(1)\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tworker.Quit()\n\t\t\t\t\t\terrorsChan <- ErrWorkerQuitGracefully\n\t\t\t\t\t\tsignalWG.Done()\n\t\t\t\t\t}()\n\t\t\t\t} else {\n\t\t\t\t\t// Abort the program when user hits Ctrl+C second time in a row\n\t\t\t\t\terrorsChan <- ErrWorkerQuitAbruptly\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n}\n\n// CustomQueue returns Custom Queue of the running worker process\nfunc (worker *Worker) CustomQueue() string {\n\treturn worker.Queue\n}\n\n// Quit tears down the running worker process\nfunc (worker *Worker) Quit() {\n\tworker.server.GetBroker().StopConsuming()\n}\n\n// Process handles received tasks and triggers success/error callbacks\nfunc (worker *Worker) Process(signature *tasks.Signature) error {\n\t// If the task is not registered with this worker, do not continue\n\t// but only return nil as we do not want to restart the worker process\n\tif !worker.server.IsTaskRegistered(signature.Name) {\n\t\treturn nil\n\t}\n\n\ttaskFunc, err := worker.server.GetRegisteredTask(signature.Name)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\t// Update task state to RECEIVED\n\tif err = worker.server.GetBackend().SetStateReceived(signature); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'received' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t// Prepare task for processing\n\ttask, err := tasks.NewWithSignature(taskFunc, signature)\n\t// if this failed, it means the task is malformed, probably has invalid\n\t// signature, go directly to task failed without checking whether to retry\n\tif err != nil {\n\t\tworker.taskFailed(signature, err)\n\t\treturn err\n\t}\n\n\t// try to extract trace span from headers and add it to the function context\n\t// so it can be used inside the function if it has context.Context as the first\n\t// argument. Start a new span if it isn't found.\n\ttaskSpan := tracing.StartSpanFromHeaders(signature.Headers, signature.Name)\n\ttracing.AnnotateSpanWithSignatureInfo(taskSpan, signature)\n\ttask.Context = opentracing.ContextWithSpan(task.Context, taskSpan)\n\n\t// Update task state to STARTED\n\tif err = worker.server.GetBackend().SetStateStarted(signature); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'started' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t//Run handler before the task is called\n\tif worker.preTaskHandler != nil {\n\t\tworker.preTaskHandler(signature)\n\t}\n\n\t//Defer run handler for the end of the task\n\tif worker.postTaskHandler != nil {\n\t\tdefer worker.postTaskHandler(signature)\n\t}\n\n\t// Call the task\n\tresults, err := task.Call()\n\tif err != nil {\n\t\t// If a tasks.ErrRetryTaskLater was returned from the task,\n\t\t// retry the task after specified duration\n\t\tretriableErr, ok := interface{}(err).(tasks.ErrRetryTaskLater)\n\t\tif ok {\n\t\t\treturn worker.retryTaskIn(signature, retriableErr.RetryIn())\n\t\t}\n\n\t\t// Otherwise, execute default retry logic based on signature.RetryCount\n\t\t// and signature.RetryTimeout values\n\t\tif signature.RetryCount > 0 {\n\t\t\treturn worker.taskRetry(signature)\n\t\t}\n\n\t\treturn worker.taskFailed(signature, err)\n\t}\n\n\treturn worker.taskSucceeded(signature, results)\n}\n\n// retryTask decrements RetryCount counter and republishes the task to the queue\nfunc (worker *Worker) taskRetry(signature *tasks.Signature) error {\n\t// Update task state to RETRY\n\tif err := worker.server.GetBackend().SetStateRetry(signature); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'retry' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t// Decrement the retry counter, when it reaches 0, we won't retry again\n\tsignature.RetryCount--\n\n\t// Increase retry timeout\n\tsignature.RetryTimeout = retry.FibonacciNext(signature.RetryTimeout)\n\n\t// Delay task by signature.RetryTimeout seconds\n\teta := time.Now().UTC().Add(time.Second * time.Duration(signature.RetryTimeout))\n\tsignature.ETA = &eta\n\n\tlog.WARNING.Printf(\"Task %s failed. Going to retry in %d seconds.\", signature.UUID, signature.RetryTimeout)\n\n\t// Send the task back to the queue\n\t_, err := worker.server.SendTask(signature)\n\treturn err\n}\n\n// taskRetryIn republishes the task to the queue with ETA of now + retryIn.Seconds()\nfunc (worker *Worker) retryTaskIn(signature *tasks.Signature, retryIn time.Duration) error {\n\t// Update task state to RETRY\n\tif err := worker.server.GetBackend().SetStateRetry(signature); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'retry' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t// Delay task by retryIn duration\n\teta := time.Now().UTC().Add(retryIn)\n\tsignature.ETA = &eta\n\n\tlog.WARNING.Printf(\"Task %s failed. Going to retry in %.0f seconds.\", signature.UUID, retryIn.Seconds())\n\n\t// Send the task back to the queue\n\t_, err := worker.server.SendTask(signature)\n\treturn err\n}\n\n// taskSucceeded updates the task state and triggers success callbacks or a\n// chord callback if this was the last task of a group with a chord callback\nfunc (worker *Worker) taskSucceeded(signature *tasks.Signature, taskResults []*tasks.TaskResult) error {\n\t// Update task state to SUCCESS\n\tif err := worker.server.GetBackend().SetStateSuccess(signature, taskResults); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'success' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\t// Log human readable results of the processed task\n\tvar debugResults = \"[]\"\n\tresults, err := tasks.ReflectTaskResults(taskResults)\n\tif err != nil {\n\t\tlog.WARNING.Print(err)\n\t} else {\n\t\tdebugResults = tasks.HumanReadableResults(results)\n\t}\n\tlog.DEBUG.Printf(\"Processed task %s. Results = %s\", signature.UUID, debugResults)\n\n\t// Trigger success callbacks\n\n\tfor _, successTask := range signature.OnSuccess {\n\t\tif signature.Immutable == false {\n\t\t\t// Pass results of the task to success callbacks\n\t\t\tfor _, taskResult := range taskResults {\n\t\t\t\tsuccessTask.Args = append(successTask.Args, tasks.Arg{\n\t\t\t\t\tType:  taskResult.Type,\n\t\t\t\t\tValue: taskResult.Value,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tworker.server.SendTask(successTask)\n\t}\n\n\t// If the task was not part of a group, just return\n\tif signature.GroupUUID == \"\" {\n\t\treturn nil\n\t}\n\n\t// There is no chord callback, just return\n\tif signature.ChordCallback == nil {\n\t\treturn nil\n\t}\n\n\t// Check if all task in the group has completed\n\tgroupCompleted, err := worker.server.GetBackend().GroupCompleted(\n\t\tsignature.GroupUUID,\n\t\tsignature.GroupTaskCount,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Completed check for group %s returned error: %s\", signature.GroupUUID, err)\n\t}\n\n\t// If the group has not yet completed, just return\n\tif !groupCompleted {\n\t\treturn nil\n\t}\n\n\t// Defer purging of group meta queue if we are using AMQP backend\n\tif worker.hasAMQPBackend() {\n\t\tdefer worker.server.GetBackend().PurgeGroupMeta(signature.GroupUUID)\n\t}\n\n\t// Trigger chord callback\n\tshouldTrigger, err := worker.server.GetBackend().TriggerChord(signature.GroupUUID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Triggering chord for group %s returned error: %s\", signature.GroupUUID, err)\n\t}\n\n\t// Chord has already been triggered\n\tif !shouldTrigger {\n\t\treturn nil\n\t}\n\n\t// Get task states\n\ttaskStates, err := worker.server.GetBackend().GroupTaskStates(\n\t\tsignature.GroupUUID,\n\t\tsignature.GroupTaskCount,\n\t)\n\tif err != nil {\n\t\tlog.ERROR.Printf(\n\t\t\t\"Failed to get tasks states for group:[%s]. Task count:[%d]. The chord may not be triggered. Error:[%s]\",\n\t\t\tsignature.GroupUUID,\n\t\t\tsignature.GroupTaskCount,\n\t\t\terr,\n\t\t)\n\t\treturn nil\n\t}\n\n\t// Append group tasks' return values to chord task if it's not immutable\n\tfor _, taskState := range taskStates {\n\t\tif !taskState.IsSuccess() {\n\t\t\treturn nil\n\t\t}\n\n\t\tif signature.ChordCallback.Immutable == false {\n\t\t\t// Pass results of the task to the chord callback\n\t\t\tfor _, taskResult := range taskState.Results {\n\t\t\t\tsignature.ChordCallback.Args = append(signature.ChordCallback.Args, tasks.Arg{\n\t\t\t\t\tType:  taskResult.Type,\n\t\t\t\t\tValue: taskResult.Value,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// Send the chord task\n\t_, err = worker.server.SendTask(signature.ChordCallback)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// taskFailed updates the task state and triggers error callbacks\nfunc (worker *Worker) taskFailed(signature *tasks.Signature, taskErr error) error {\n\t// Update task state to FAILURE\n\tif err := worker.server.GetBackend().SetStateFailure(signature, taskErr.Error()); err != nil {\n\t\treturn fmt.Errorf(\"Set state to 'failure' for task %s returned error: %s\", signature.UUID, err)\n\t}\n\n\tif worker.errorHandler != nil {\n\t\tworker.errorHandler(taskErr)\n\t} else {\n\t\tlog.ERROR.Printf(\"Failed processing task %s. Error = %v\", signature.UUID, taskErr)\n\t}\n\n\t// Trigger error callbacks\n\tfor _, errorTask := range signature.OnError {\n\t\t// Pass error as a first argument to error callbacks\n\t\targs := append([]tasks.Arg{{\n\t\t\tType:  \"string\",\n\t\t\tValue: taskErr.Error(),\n\t\t}}, errorTask.Args...)\n\t\terrorTask.Args = args\n\t\tworker.server.SendTask(errorTask)\n\t}\n\n\tif signature.StopTaskDeletionOnError {\n\t\treturn errs.ErrStopTaskDeletion\n\t}\n\n\treturn nil\n}\n\n// Returns true if the worker uses AMQP backend\nfunc (worker *Worker) hasAMQPBackend() bool {\n\t_, ok := worker.server.GetBackend().(*amqp.Backend)\n\treturn ok\n}\n\n// SetErrorHandler sets a custom error handler for task errors\n// A default behavior is just to log the error after all the retry attempts fail\nfunc (worker *Worker) SetErrorHandler(handler func(err error)) {\n\tworker.errorHandler = handler\n}\n\n//SetPreTaskHandler sets a custom handler func before a job is started\nfunc (worker *Worker) SetPreTaskHandler(handler func(*tasks.Signature)) {\n\tworker.preTaskHandler = handler\n}\n\n//SetPostTaskHandler sets a custom handler for the end of a job\nfunc (worker *Worker) SetPostTaskHandler(handler func(*tasks.Signature)) {\n\tworker.postTaskHandler = handler\n}\n\n//SetPreConsumeHandler sets a custom handler for the end of a job\nfunc (worker *Worker) SetPreConsumeHandler(handler func(*Worker) bool) {\n\tworker.preConsumeHandler = handler\n}\n\n//GetServer returns server\nfunc (worker *Worker) GetServer() *Server {\n\treturn worker.server\n}\n\n//\nfunc (worker *Worker) PreConsumeHandler() bool {\n\tif worker.preConsumeHandler == nil {\n\t\treturn true\n\t}\n\n\treturn worker.preConsumeHandler(worker)\n}\n\nfunc RedactURL(urlString string) string {\n\tu, err := url.Parse(urlString)\n\tif err != nil {\n\t\treturn urlString\n\t}\n\treturn fmt.Sprintf(\"%s://%s\", u.Scheme, u.Host)\n}\n"
  },
  {
    "path": "v2/worker_test.go",
    "content": "package machinery_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/RichardKnop/machinery/v2\"\n)\n\nfunc TestRedactURL(t *testing.T) {\n\tt.Parallel()\n\n\tbroker := \"amqp://guest:guest@localhost:5672\"\n\tredactedURL := machinery.RedactURL(broker)\n\tassert.Equal(t, \"amqp://localhost:5672\", redactedURL)\n}\n\nfunc TestPreConsumeHandler(t *testing.T) {\n\tt.Parallel()\n\t\n\tworker := &machinery.Worker{}\n\n\tworker.SetPreConsumeHandler(SamplePreConsumeHandler)\n\tassert.True(t, worker.PreConsumeHandler())\n}\n\nfunc SamplePreConsumeHandler(w *machinery.Worker) bool {\n\treturn true\n}\n"
  },
  {
    "path": "wait-for-it.sh",
    "content": "#!/usr/bin/env bash\n# https://github.com/vishnubob/wait-for-it/blob/master/wait-for-it.sh\n#   Use this script to test if a given TCP host/port are available\n\ncmdname=$(basename $0)\n\nechoerr() { if [[ $QUIET -ne 1 ]]; then echo \"$@\" 1>&2; fi }\n\nusage()\n{\n    cat << USAGE >&2\nUsage:\n    $cmdname host:port [-s] [-t timeout] [-- command args]\n    -h HOST | --host=HOST       Host or IP under test\n    -p PORT | --port=PORT       TCP port under test\n                                Alternatively, you specify the host and port as host:port\n    -s | --strict               Only execute subcommand if the test succeeds\n    -q | --quiet                Don't output any status messages\n    -t TIMEOUT | --timeout=TIMEOUT\n                                Timeout in seconds, zero for no timeout\n    -- COMMAND ARGS             Execute command with args after the test finishes\nUSAGE\n    exit 1\n}\n\nwait_for()\n{\n    if [[ $TIMEOUT -gt 0 ]]; then\n        echoerr \"$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT\"\n    else\n        echoerr \"$cmdname: waiting for $HOST:$PORT without a timeout\"\n    fi\n    start_ts=$(date +%s)\n    while :\n    do\n        if [[ $ISBUSY -eq 1 ]]; then\n            nc -z $HOST $PORT\n            result=$?\n        else\n            (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1\n            result=$?\n        fi\n        if [[ $result -eq 0 ]]; then\n            end_ts=$(date +%s)\n            echoerr \"$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds\"\n            break\n        fi\n        sleep 1\n    done\n    return $result\n}\n\nwait_for_wrapper()\n{\n    # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692\n    if [[ $QUIET -eq 1 ]]; then\n        timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &\n    else\n        timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &\n    fi\n    PID=$!\n    trap \"kill -INT -$PID\" INT\n    wait $PID\n    RESULT=$?\n    if [[ $RESULT -ne 0 ]]; then\n        echoerr \"$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT\"\n    fi\n    return $RESULT\n}\n\n# process arguments\nwhile [[ $# -gt 0 ]]\ndo\n    case \"$1\" in\n        *:* )\n        hostport=(${1//:/ })\n        HOST=${hostport[0]}\n        PORT=${hostport[1]}\n        shift 1\n        ;;\n        --child)\n        CHILD=1\n        shift 1\n        ;;\n        -q | --quiet)\n        QUIET=1\n        shift 1\n        ;;\n        -s | --strict)\n        STRICT=1\n        shift 1\n        ;;\n        -h)\n        HOST=\"$2\"\n        if [[ $HOST == \"\" ]]; then break; fi\n        shift 2\n        ;;\n        --host=*)\n        HOST=\"${1#*=}\"\n        shift 1\n        ;;\n        -p)\n        PORT=\"$2\"\n        if [[ $PORT == \"\" ]]; then break; fi\n        shift 2\n        ;;\n        --port=*)\n        PORT=\"${1#*=}\"\n        shift 1\n        ;;\n        -t)\n        TIMEOUT=\"$2\"\n        if [[ $TIMEOUT == \"\" ]]; then break; fi\n        shift 2\n        ;;\n        --timeout=*)\n        TIMEOUT=\"${1#*=}\"\n        shift 1\n        ;;\n        --)\n        shift\n        CLI=(\"$@\")\n        break\n        ;;\n        --help)\n        usage\n        ;;\n        *)\n        echoerr \"Unknown argument: $1\"\n        usage\n        ;;\n    esac\ndone\n\nif [[ \"$HOST\" == \"\" || \"$PORT\" == \"\" ]]; then\n    echoerr \"Error: you need to provide a host and port to test.\"\n    usage\nfi\n\nTIMEOUT=${TIMEOUT:-15}\nSTRICT=${STRICT:-0}\nCHILD=${CHILD:-0}\nQUIET=${QUIET:-0}\n\n# check to see if timeout is from busybox?\nTIMEOUT_PATH=$(realpath $(which timeout))\nif [[ $TIMEOUT_PATH =~ \"busybox\" ]]; then\n        ISBUSY=1\n        BUSYTIMEFLAG=\"-t\"\nelse\n        ISBUSY=0\n        BUSYTIMEFLAG=\"\"\nfi\n\nif [[ $CHILD -gt 0 ]]; then\n    wait_for\n    RESULT=$?\n    exit $RESULT\nelse\n    if [[ $TIMEOUT -gt 0 ]]; then\n        wait_for_wrapper\n        RESULT=$?\n    else\n        wait_for\n        RESULT=$?\n    fi\nfi\n\nif [[ $CLI != \"\" ]]; then\n    if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then\n        echoerr \"$cmdname: strict mode, refusing to execute subprocess\"\n        exit $RESULT\n    fi\n    exec \"${CLI[@]}\"\nelse\n    exit $RESULT\nfi\n"
  }
]