CouchDB
  1. CouchDB
  2. COUCHDB-1289

heartbeats skipped when continuous changes feed filter function produces no results

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 1.2
    • Component/s: Database Core
    • Labels:
    • Skill Level:
      New Contributors Level (Easy)

      Description

      if the changes feed has a filter function that produces no results, db_updated messages will still be sent and the heartbeat timeout will never be reached.

        Issue Links

          Activity

          Hide
          Paul Joseph Davis added a comment -

          There are two aspects to this that need to be considered. We've fixed both of these in BigCouch but I'm not sure if we've the first one.

          The first one is that if you have a large range of documents (ordered by update seq) that fail to pass a filter, there will be no heartbeat sent. Basically, the changes feed thinks its making process all hunky dory but nothing is being sent, and we never checked that its taking us too long to filter all these docs.

          The second version of this is slightly more insidious. Basically, the timeouts we had in BigCouch were receive timeouts in two places (no db updates, and while processing in case everything is filtered). What can end up happening is that updates to the db can end up trickling in that all fail to make it through the filter function. This means that the changes loop thinks its working again but is not hitting either timeout clause to send the heartbeat.

          The clustered calls in BigCouch are different enough that the patches aren't exactly applicable, but reading the code, CouchDB is at least susceptible to the second version and possible the first as well.

          Show
          Paul Joseph Davis added a comment - There are two aspects to this that need to be considered. We've fixed both of these in BigCouch but I'm not sure if we've the first one. The first one is that if you have a large range of documents (ordered by update seq) that fail to pass a filter, there will be no heartbeat sent. Basically, the changes feed thinks its making process all hunky dory but nothing is being sent, and we never checked that its taking us too long to filter all these docs. The second version of this is slightly more insidious. Basically, the timeouts we had in BigCouch were receive timeouts in two places (no db updates, and while processing in case everything is filtered). What can end up happening is that updates to the db can end up trickling in that all fail to make it through the filter function. This means that the changes loop thinks its working again but is not hitting either timeout clause to send the heartbeat. The clustered calls in BigCouch are different enough that the patches aren't exactly applicable, but reading the code, CouchDB is at least susceptible to the second version and possible the first as well.
          Hide
          Bob Dionne added a comment -

          The issue in couchdb is that the timeout function is called in wait_db_updated and never triggered if an update occurs, which is problematic when the update leads to no changes sent. I've tried to find a minimally disruptive fix, but this is the best[1] I can do so far modulo refactoring. I think a better solution that removes the timeout function from wait_db_updated will be the way to go but this will be a more extensive refactoring. As an aside I think this line is also a bug[2] and should perhaps read like the other version of changes_enumerator

          [1] https://github.com/bdionne/couchdb/commit/a4d26f9b0c82c0423521cbe
          [2] https://github.com/bdionne/couchdb/blob/a4d26f9b0c82c0423521cbe5a7d5f17354aa4b9a/src/couchdb/couch_changes.erl#L363

          Show
          Bob Dionne added a comment - The issue in couchdb is that the timeout function is called in wait_db_updated and never triggered if an update occurs, which is problematic when the update leads to no changes sent. I've tried to find a minimally disruptive fix, but this is the best [1] I can do so far modulo refactoring. I think a better solution that removes the timeout function from wait_db_updated will be the way to go but this will be a more extensive refactoring. As an aside I think this line is also a bug [2] and should perhaps read like the other version of changes_enumerator [1] https://github.com/bdionne/couchdb/commit/a4d26f9b0c82c0423521cbe [2] https://github.com/bdionne/couchdb/blob/a4d26f9b0c82c0423521cbe5a7d5f17354aa4b9a/src/couchdb/couch_changes.erl#L363
          Hide
          Filipe Manana added a comment - - edited

          Good finding Bob.
          What about using a simple function that is called before processing each changes row and uses the process dictionary to reduce code size/complexity, along the lines:

          maybe_timeout(TimeoutFun, Acc) ->
          Now = now(),
          Before = case get(changes_timeout) of
          undefined ->
          Now;
          OldNow ->
          OldNow
          end,
          case timer:diff(Now, Before) >= Timeout of
          true ->
          Acc2 = TimeoutFun(Acc),
          put(changes_timeout, Now),
          Acc2;
          false ->
          Acc
          end.

          Probably it misses some cases, i haven't thought about this issue before.

          Show
          Filipe Manana added a comment - - edited Good finding Bob. What about using a simple function that is called before processing each changes row and uses the process dictionary to reduce code size/complexity, along the lines: maybe_timeout(TimeoutFun, Acc) -> Now = now(), Before = case get(changes_timeout) of undefined -> Now; OldNow -> OldNow end, case timer:diff(Now, Before) >= Timeout of true -> Acc2 = TimeoutFun(Acc), put(changes_timeout, Now), Acc2; false -> Acc end. Probably it misses some cases, i haven't thought about this issue before.
          Hide
          Bob Dionne added a comment -

          Good idea Filipe, much as I dislike using the heap, this does make the code simpler[1]. Still need the boolean `changes_sent` in the `changes_acc` to know something actually got sent, but it's better

          [1] https://github.com/bdionne/couchdb/commit/8b2bf85fcade

          Show
          Bob Dionne added a comment - Good idea Filipe, much as I dislike using the heap, this does make the code simpler [1] . Still need the boolean `changes_sent` in the `changes_acc` to know something actually got sent, but it's better [1] https://github.com/bdionne/couchdb/commit/8b2bf85fcade
          Hide
          Bob Dionne added a comment -

          This patch against current master fixes the heartbeat issue

          Show
          Bob Dionne added a comment - This patch against current master fixes the heartbeat issue
          Hide
          Bob Dionne added a comment -
          Show
          Bob Dionne added a comment - Added a failing etap test for this [1] [1] https://github.com/bdionne/couchdb/compare/master...1289-heartbeats-skipped2
          Hide
          Filipe Manana added a comment -

          Bob looks good.

          Just have a few comments:

          1) The indentation is a bit messed up, like the "if" expression "true" clause and its inner "cause" expression: https://github.com/bdionne/couchdb/compare/master...1289-heartbeats-skipped2#L0R398

          2) This issue doesn't happen exclusively once the code finishes folding the seq tree and starts listening for db update events. Lets say we have a heartbeat timeout of 10 seconds, a seq tree with millions of entries, a filter functions which returns false for many consecutive entries. It's quite possible that before folding the entire seq tree snapshot and sending the first changes row which passes the filter (if any) the heartbeat timeout is exceeded. Makes sense?

          3) The test can have way less duplicated code (spawn_consumer_heartbeat is basically a copy-paste of spawn_consumer). I made a few changes to it in my repo: https://github.com/fdmanana/couchdb/commit/2af5d56b909449569d60573f8118d7187e9334ca

          thanks for working on this one

          Show
          Filipe Manana added a comment - Bob looks good. Just have a few comments: 1) The indentation is a bit messed up, like the "if" expression "true" clause and its inner "cause" expression: https://github.com/bdionne/couchdb/compare/master...1289-heartbeats-skipped2#L0R398 2) This issue doesn't happen exclusively once the code finishes folding the seq tree and starts listening for db update events. Lets say we have a heartbeat timeout of 10 seconds, a seq tree with millions of entries, a filter functions which returns false for many consecutive entries. It's quite possible that before folding the entire seq tree snapshot and sending the first changes row which passes the filter (if any) the heartbeat timeout is exceeded. Makes sense? 3) The test can have way less duplicated code (spawn_consumer_heartbeat is basically a copy-paste of spawn_consumer). I made a few changes to it in my repo: https://github.com/fdmanana/couchdb/commit/2af5d56b909449569d60573f8118d7187e9334ca thanks for working on this one
          Hide
          Bob Dionne added a comment -

          Filipe, thanks for the comments:

          3. It took me a bit to figure out how this test was working at all, so I knew I could probably simplify it further. This is better, I always forget to use some state on the heap for these cases.

          2. I'll revisit this. I think you're saying I've missed some cases

          1. Meh, this one is getting old. This indenting looks good to me. People claim to not care too much yet keep bringing it up. The problem I have is I like to edit using modes, not manually (which from what I can tell is how most people edit couchdb source). The other problem I have is that the only spec I know for formatting erlang is what's in the erlang.el mode. There's a claim about a couchdb style but this seems to vary with each committer. So I'm increasingly reluctant to "fix" formatting when no one seems to be able to specify the rules.

          Best,

          Bob

          Show
          Bob Dionne added a comment - Filipe, thanks for the comments: 3. It took me a bit to figure out how this test was working at all, so I knew I could probably simplify it further. This is better, I always forget to use some state on the heap for these cases. 2. I'll revisit this. I think you're saying I've missed some cases 1. Meh, this one is getting old. This indenting looks good to me. People claim to not care too much yet keep bringing it up. The problem I have is I like to edit using modes, not manually (which from what I can tell is how most people edit couchdb source). The other problem I have is that the only spec I know for formatting erlang is what's in the erlang.el mode. There's a claim about a couchdb style but this seems to vary with each committer. So I'm increasingly reluctant to "fix" formatting when no one seems to be able to specify the rules. Best, Bob
          Hide
          Filipe Manana added a comment -

          Bob, for the indentation we have those rules in the wiki: http://wiki.apache.org/couchdb/Coding_Standards.
          For e.g. the "true" at: https://github.com/bdionne/couchdb/compare/master...1289-heartbeats-skipped2#L0R401
          Is not aligned with corresponding "if" (line 398) neither it is on a column multiple of 4 spaces.

          Yep, for 2) it's an extra scenario. Probably after passing a row to the filter function, if the filter returns "false" and the time difference between "now()" and the time the last row was sent is greater than the threshold, it would call the callback with "timeout" (so it sends the newline to http clients). If the filter returns "true", set that "last row sent time" to "now()". The only case where this wouldn't work is if the filter function takes "heartbeat" seconds to evaluate a changes row (possibly very unlikely).
          This can probably be addressed separately, I have no objection about that.

          Show
          Filipe Manana added a comment - Bob, for the indentation we have those rules in the wiki: http://wiki.apache.org/couchdb/Coding_Standards . For e.g. the "true" at: https://github.com/bdionne/couchdb/compare/master...1289-heartbeats-skipped2#L0R401 Is not aligned with corresponding "if" (line 398) neither it is on a column multiple of 4 spaces. Yep, for 2) it's an extra scenario. Probably after passing a row to the filter function, if the filter returns "false" and the time difference between "now()" and the time the last row was sent is greater than the threshold, it would call the callback with "timeout" (so it sends the newline to http clients). If the filter returns "true", set that "last row sent time" to "now()". The only case where this wouldn't work is if the filter function takes "heartbeat" seconds to evaluate a changes row (possibly very unlikely). This can probably be addressed separately, I have no objection about that.
          Hide
          Bob Dionne added a comment -

          Filipe,

          I've seen these guidelines. I think this code adheres to them. If it doesn't I'll fix it. The "true" clause is aligned with the initial "Done" boolean. I think this is acceptable. I know you and Adam think these should be under the "If", similar to the "case", but there is many places in the couchdb code where this is not true. I'll put it under the if since the's the way it is elsewhere in this same file.

          How about for case statements? Should the clauses line up under the "case"?

          Cheers,

          Bob

          Show
          Bob Dionne added a comment - Filipe, I've seen these guidelines. I think this code adheres to them. If it doesn't I'll fix it. The "true" clause is aligned with the initial "Done" boolean. I think this is acceptable. I know you and Adam think these should be under the "If", similar to the "case", but there is many places in the couchdb code where this is not true. I'll put it under the if since the's the way it is elsewhere in this same file. How about for case statements? Should the clauses line up under the "case"? Cheers, Bob
          Hide
          Filipe Manana added a comment -

          Bob, the thing about the "if" is that the "true" clause is not a column multiple of 4 (it's on column 11), which violates the soft convention we have.
          As for the cases, some indent the clauses to the "case" keyword, others indent it one more level (4 spaces). I think either is readable and don't care much about it unless it starts to be unreadable (not on 4 space multiples, of a big mix of both styles in the same module/region).

          Show
          Filipe Manana added a comment - Bob, the thing about the "if" is that the "true" clause is not a column multiple of 4 (it's on column 11), which violates the soft convention we have. As for the cases, some indent the clauses to the "case" keyword, others indent it one more level (4 spaces). I think either is readable and don't care much about it unless it starts to be unreadable (not on 4 space multiples, of a big mix of both styles in the same module/region).
          Hide
          Bob Dionne added a comment -

          cool, I'll fix it.

          Show
          Bob Dionne added a comment - cool, I'll fix it.
          Hide
          Bob Dionne added a comment -

          Filipe,

          Sorry to be dense about this, but what you describe in #2 is precisely what is happening. `maybe_timeout` takes a boolean `Sent` which comes from the changes_sent field in the Accumulator. It's set depending on whether or not the filter function is successful. `maybe_timeout' then uses this to either reset the timer in the heap to now() or not depending on the timeout.

          Is is easy enough for you to extend the failing test to demo this missing case?

          Thanks,

          Bob

          Show
          Bob Dionne added a comment - Filipe, Sorry to be dense about this, but what you describe in #2 is precisely what is happening. `maybe_timeout` takes a boolean `Sent` which comes from the changes_sent field in the Accumulator. It's set depending on whether or not the filter function is successful. `maybe_timeout' then uses this to either reset the timer in the heap to now() or not depending on the timeout. Is is easy enough for you to extend the failing test to demo this missing case? Thanks, Bob
          Hide
          Filipe Manana added a comment -

          Bob, np.

          Looking at the whole context, not limited to see only the diff, I think you're right. The patch addresses the case I mentioned before, since it each tree fold has changes_sent set to false for initial accumulator and the changes_enumerator callback sets it to true once a row is accepted by the filter. However this is set only for the "continuous" clause of changes_enumerator. The other clause should set changes_set to true according to the same condition.

          Show
          Filipe Manana added a comment - Bob, np. Looking at the whole context, not limited to see only the diff, I think you're right. The patch addresses the case I mentioned before, since it each tree fold has changes_sent set to false for initial accumulator and the changes_enumerator callback sets it to true once a row is accepted by the filter. However this is set only for the "continuous" clause of changes_enumerator. The other clause should set changes_set to true according to the same condition.
          Hide
          Bob Dionne added a comment -

          Filipe,

          Ah, thanks, it looks like heartbeat was never supported in the feed="normal" case. This next draft[1] handles this case also. I cherry-picked your cleaned up etap to test with and it all passes. There is still a bug to fix and looking at get_changes_timeout I see there are other cases missing, .eg. heartbeat might be undefined and timeout infinity and I'm not dealing with

          {stop, UsrAcc}

          . I've added the Timeout and TimeoutFun to the changes_acc record to try and reduce the number of args to send_changes.

          Anyway, it needs more simplifying and refactoring. I'll upload a patch when there's something for you to review.

          Cheers,

          Bob

          [1] https://github.com/bdionne/couchdb/compare/master...1289-heartbeats-skipped2

          Show
          Bob Dionne added a comment - Filipe, Ah, thanks, it looks like heartbeat was never supported in the feed="normal" case. This next draft [1] handles this case also. I cherry-picked your cleaned up etap to test with and it all passes. There is still a bug to fix and looking at get_changes_timeout I see there are other cases missing, .eg. heartbeat might be undefined and timeout infinity and I'm not dealing with {stop, UsrAcc} . I've added the Timeout and TimeoutFun to the changes_acc record to try and reduce the number of args to send_changes. Anyway, it needs more simplifying and refactoring. I'll upload a patch when there's something for you to review. Cheers, Bob [1] https://github.com/bdionne/couchdb/compare/master...1289-heartbeats-skipped2
          Hide
          Bob Dionne added a comment -

          second draft of patch. Filipe, this handles both cases now and is refactored to be a bit simpler.

          Show
          Bob Dionne added a comment - second draft of patch. Filipe, this handles both cases now and is refactored to be a bit simpler.
          Hide
          Filipe Manana added a comment -

          Looks good Bob, only a small regression compared to the previous versions, which is:

          The last time a row was sent is never reset to "now()" once the filter function returns true. Basically the maybe_timeout(true, ...) function clause is never used in this new version.
          This could cause unnecessary timeouts when the filter functions returns false several times in a row, then returns true (1 or more times in a row) and then starts returning false again. Here's what I mean:

          https://github.com/fdmanana/couchdb/commit/31c80e34875932969716791b4b8adf374d240821

          A test for this case would be awesome

          Thanks for working on this, this has been affecting many users for a long time (when doing filtered replications).

          Show
          Filipe Manana added a comment - Looks good Bob, only a small regression compared to the previous versions, which is: The last time a row was sent is never reset to "now()" once the filter function returns true. Basically the maybe_timeout(true, ...) function clause is never used in this new version. This could cause unnecessary timeouts when the filter functions returns false several times in a row, then returns true (1 or more times in a row) and then starts returning false again. Here's what I mean: https://github.com/fdmanana/couchdb/commit/31c80e34875932969716791b4b8adf374d240821 A test for this case would be awesome Thanks for working on this, this has been affecting many users for a long time (when doing filtered replications).
          Hide
          Bob Dionne added a comment -

          Thanks for the review Filipe. I pushed your changes on the branch[1]. I'll add an additional etap for this case and rework the two patches when I return later this morning. It's nice to get the maybe_timeout test into changes_enumerator and out of the keep_sending_changes loop, as it makes both the continuous and normal cases the same.

          [1] https://github.com/bdionne/couchdb/commit/72cd7a5a214c0f1d810d7c9d8890645

          Show
          Bob Dionne added a comment - Thanks for the review Filipe. I pushed your changes on the branch [1] . I'll add an additional etap for this case and rework the two patches when I return later this morning. It's nice to get the maybe_timeout test into changes_enumerator and out of the keep_sending_changes loop, as it makes both the continuous and normal cases the same. [1] https://github.com/bdionne/couchdb/commit/72cd7a5a214c0f1d810d7c9d8890645
          Hide
          Bob Dionne added a comment -
          Show
          Bob Dionne added a comment - Add etap test for this last case [1] [1] https://github.com/bdionne/couchdb/compare/master...1289-heartbeats-skipped2
          Hide
          Filipe Manana added a comment -

          Bob, looks good.

          I was testing this against a very large database (http://fdmanana.iriscouch.com/many_docs) by querying _changes without an heartbeat parameter and with a filter that returned false for the first half of the changes of the db.
          It turns out that about 1 minute after the feed terminated without folding the entire seq tree (last_seq in the response was not the current db's seq number).

          So what happens is that when no heartbeat is given, a default TimeoutFun which returns

          {stop, Acc}

          (always) is used:

          https://github.com/fdmanana/couchdb/blob/c574d29bb92f2b61688b52e2e03548b05cc4a163/src/couchdb/couch_changes.erl#L239

          So we have 2 distinct concepts in the changes feed: heartbeat and timeout.
          I think renaming "maybe_timeout" to "maybe_heartbeat" makes it more clear, and call the function only if no heartbeat was specified. I made quick code change to test this:

          https://github.com/fdmanana/couchdb/commit/c574d29bb92f2b61688b52e2e03548b05cc4a163

          Makes sense?

          thanks

          Show
          Filipe Manana added a comment - Bob, looks good. I was testing this against a very large database ( http://fdmanana.iriscouch.com/many_docs ) by querying _changes without an heartbeat parameter and with a filter that returned false for the first half of the changes of the db. It turns out that about 1 minute after the feed terminated without folding the entire seq tree (last_seq in the response was not the current db's seq number). So what happens is that when no heartbeat is given, a default TimeoutFun which returns {stop, Acc} (always) is used: https://github.com/fdmanana/couchdb/blob/c574d29bb92f2b61688b52e2e03548b05cc4a163/src/couchdb/couch_changes.erl#L239 So we have 2 distinct concepts in the changes feed: heartbeat and timeout. I think renaming "maybe_timeout" to "maybe_heartbeat" makes it more clear, and call the function only if no heartbeat was specified. I made quick code change to test this: https://github.com/fdmanana/couchdb/commit/c574d29bb92f2b61688b52e2e03548b05cc4a163 Makes sense? thanks
          Hide
          Bob Dionne added a comment -

          I think this is correct, I certainly agree with the name change. So in these "feed=normal" cases that are long running you won't timeout at all?

          Show
          Bob Dionne added a comment - I think this is correct, I certainly agree with the name change. So in these "feed=normal" cases that are long running you won't timeout at all?
          Hide
          Filipe Manana added a comment -

          Yep, so before we never called TimeoutFun for feed=normal, or better said, when folding the seq tree.

          I'm finding the existence of both a timeout and heartbeat quite confusing.

          But it makes sense to send heartbeats while folding the seq tree (if the client specified an heartbeat). As you said before as well, renaming "reset_timeout" to "reset_heartbeat" is saner.

          Show
          Filipe Manana added a comment - Yep, so before we never called TimeoutFun for feed=normal, or better said, when folding the seq tree. I'm finding the existence of both a timeout and heartbeat quite confusing. But it makes sense to send heartbeats while folding the seq tree (if the client specified an heartbeat). As you said before as well, renaming "reset_timeout" to "reset_heartbeat" is saner.
          Hide
          Bob Dionne added a comment -

          Filipe,

          I pulled your last commit, fixed those nits, rebased, squashed and formatted two new patches. I made you author of one, since it doesn't look like git supports multiple authors. Thanks, this one was gnarly, I'm hopeful we got all the edges,

          Bob

          Show
          Bob Dionne added a comment - Filipe, I pulled your last commit, fixed those nits, rebased, squashed and formatted two new patches. I made you author of one, since it doesn't look like git supports multiple authors. Thanks, this one was gnarly, I'm hopeful we got all the edges, Bob
          Hide
          Filipe Manana added a comment -

          Bob, +1.
          You did most of the hard work, no need to set me as the author for one of them.
          Lets push it to master and 1.2.x?

          Thanks

          Show
          Filipe Manana added a comment - Bob, +1. You did most of the hard work, no need to set me as the author for one of them. Lets push it to master and 1.2.x? Thanks
          Hide
          Bob Dionne added a comment -

          thanks Filipe

          Show
          Bob Dionne added a comment - thanks Filipe
          Hide
          Filipe Manana added a comment -

          Bob, just caught another case where the default TimeoutFun will be called (which stops the streaming immediately):

          From 7160021512161e3fa8f83165adc1a64285da6518 Mon Sep 17 00:00:00 2001
          From: Filipe David Borba Manana <fdmanana@apache.org>
          Date: Mon, 28 Nov 2011 12:30:50 +0000
          Subject: [PATCH 2/2] Make reset_heartbeat/0 a no-op

          If no heartbeat option was given, the couch_changes heartbeat
          function should really be a no-op, that is, to not set the
          last_changes_heartbeat to the current time, otherwise the
          default TimeoutFun (when no heartbeat option is given) might
          be called which causes the changes feed to stop immediately
          before folding the whole seq tree.

          src/couchdb/couch_changes.erl | 7 ++++++-
          1 files changed, 6 insertions, 1 deletions

          diff --git a/src/couchdb/couch_changes.erl b/src/couchdb/couch_changes.erl
          index f124567..72ee346 100644
          — a/src/couchdb/couch_changes.erl
          +++ b/src/couchdb/couch_changes.erl
          @@ -531,7 +531,12 @@ get_rest_db_updated(UserAcc) ->
          end.

          reset_heartbeat() ->

          • put(last_changes_heartbeat,now()).
            + case get(last_changes_heartbeat) of
            + undefined ->
            + ok;
            + _ ->
            + put(last_changes_heartbeat, now())
            + end.

          maybe_heartbeat(Timeout, TimeoutFun, Acc) ->
          Before = get(last_changes_heartbeat),

          1.7.4.4

          Do you agree?

          Show
          Filipe Manana added a comment - Bob, just caught another case where the default TimeoutFun will be called (which stops the streaming immediately): From 7160021512161e3fa8f83165adc1a64285da6518 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana <fdmanana@apache.org> Date: Mon, 28 Nov 2011 12:30:50 +0000 Subject: [PATCH 2/2] Make reset_heartbeat/0 a no-op If no heartbeat option was given, the couch_changes heartbeat function should really be a no-op, that is, to not set the last_changes_heartbeat to the current time, otherwise the default TimeoutFun (when no heartbeat option is given) might be called which causes the changes feed to stop immediately before folding the whole seq tree. — src/couchdb/couch_changes.erl | 7 ++++++- 1 files changed, 6 insertions , 1 deletions diff --git a/src/couchdb/couch_changes.erl b/src/couchdb/couch_changes.erl index f124567..72ee346 100644 — a/src/couchdb/couch_changes.erl +++ b/src/couchdb/couch_changes.erl @@ -531,7 +531,12 @@ get_rest_db_updated(UserAcc) -> end. reset_heartbeat() -> put(last_changes_heartbeat,now()). + case get(last_changes_heartbeat) of + undefined -> + ok; + _ -> + put(last_changes_heartbeat, now()) + end. maybe_heartbeat(Timeout, TimeoutFun, Acc) -> Before = get(last_changes_heartbeat), – 1.7.4.4 Do you agree?
          Hide
          Filipe Manana added a comment -

          (Jira messed up with the patch indentation)

          Show
          Filipe Manana added a comment - (Jira messed up with the patch indentation)
          Hide
          Filipe Manana added a comment -

          Added the patch as an attachment instead

          Show
          Filipe Manana added a comment - Added the patch as an attachment instead

            People

            • Assignee:
              Bob Dionne
              Reporter:
              Bob Dionne
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development