使用clojure core.async管道处理错误

时间:2016-12-28 17:20:54

标签: error-handling clojure pipeline core.async

我试图了解使用core.async / pipeline处理错误的正确方法是什么,我的管道如下:

input     --> xf-run-computation --> first-out
first-out --> xf-run-computation --> last-out

xf-run-computation将执行http调用并返回响应。但是,其中一些响应将返回错误。处理这些错误的最佳方法是什么? 我的解决方案是在success-valueserror-values中拆分输出通道,然后将它们合并回通道:

(let [[success-values1 error-values1] (split fn-to-split first-out)
      [success-values2 error-values2] (split fn-to-split last-out)
      errors (merge [error-values1 error-values2])]
(pipeline 4 first-out xf-run-computation input)
(pipeline 4 last-out  xf-run-computation success-values1)
[last-out errors])

所以我的函数将返回最后的结果和错误。

2 个答案:

答案 0 :(得分:5)

一般来说,"""正确的方法可能取决于您的应用需求,但鉴于您的问题描述,我认为您需要考虑三件事:

  1. xf-run-computation会返回您的业务逻辑会将其视为错误的数据,
  2. xf-run-computation抛出异常并
  3. 鉴于涉及http调用,xf-run-computation的某些运行可能永远不会完成(或未及时完成)。
  4. 关于第3点,您应该考虑的第一件事是使用pipeline-blocking而不是pipeline

    我认为你的问题主要与第1点有关。基本思想是xf-run-computation的结果需要返回一个数据结构(例如地图或记录),这清楚地将结果标记为错误或成功,例如{:title nil :body nil :status "error"}。这将为您提供处理这种情况的一些选择:

    • 所有后来的代码都会忽略具有:status "error"的输入数据。即,您的xf-run-computation将包含类似(when (not (= (:status input) "error")) (run-computation input))

    • 的行
    • 您可以根据需要对pipeline - 调用和filter之间的所有结果运行过滤器(请注意,filter也可用作管道中的传感器,从而删除core.async的旧filter>filter<函数,

    • 你像你建议的那样使用async/split / Alan Thompson在他的回答中显示将错误值过滤到一个单独的错误通道。如果您无论如何都要合并这些值,则无需为第二个管道设置第二个错误通道,您只需重新使用错误通道即可。

    对于第2点,问题是xf-run-computation中的任何异常都发生在另一个线程中,并且不会简单地传播回您的调用代码。但是您可以使用ex-handler pipeline {和pipeline-blocking的{​​{1}}参数。您可以简单地过滤掉所有异常,将结果放在单独的异常通道上或尝试捕获它们并将它们转换为错误(可能将它们放回结果或其他错误通道) - 后者只有在例外为您提供了足够的信息,例如允许将异常绑定到导致异常的输入的id或其他内容。您可以在xf-run-computation中安排此操作(即catch从第三方库(例如http调用)抛出的任何异常。

    对于第3点,core.async中的规范答案将指向timeout频道,但这与pipeline相比并没有多大意义。更好的办法是确保在您的http调用中设置超时,例如http-kit的:timeout选项或clj-http的:socket-timeout:conn-timeout。请注意,这些选项通常会导致超时异常。

答案 1 :(得分:1)

这是一个做你所建议的例子。从(range 10)开始,它首先过滤掉5的倍数,然后过滤掉3的倍数。

(ns tst.clj.core
  (:use clj.core
        clojure.test )
  (:require
    [clojure.core.async :as async]
    [clojure.string :as str]
  )
)

(defn err-3 [x]
  "'fail' for multiples of 3"
  (if (zero? (mod x 3))
    (+ x 300)       ; error case
    x))             ; non-error

(defn err-5 [x]
  "'fail' for multiples of 5"
  (if (zero? (mod x 5))
    (+ x 500)       ; error case
    x))             ; non-error

(defn is-ok?
  "Returns true if the value is not 'in error' (>=100)"
  [x]
  (< x 100))

(def ch-0  (async/to-chan (range 10)))
(def ch-1  (async/chan 99))
(def ch-2  (async/chan 99))

(deftest t-2
  (let [
        _                         (async/pipeline 1 ch-1 (map err-5) ch-0)
        [ok-chan-1 fail-chan-1]   (async/split is-ok? ch-1 99 99)
        _                         (async/pipeline 1 ch-2 (map err-3) ok-chan-1)
        [ok-chan-2 fail-chan-2]   (async/split is-ok? ch-2 99 99)

        ok-vec-2                  (async/<!! (async/into [] ok-chan-2))
        fail-vec-1                (async/<!! (async/into [] fail-chan-1))
        fail-vec-2                (async/<!! (async/into [] fail-chan-2))
  ]
    (is (= ok-vec-2 [1 2 4 7 8]))
    (is (= fail-vec-1 [500 505]))
    (is (= fail-vec-2 [303 306 309]))))

我可能只是在检测到错误时将其记录下来,然后忘掉它们,而不是返回错误。