Skip to content

Conversation

maflcko
Copy link
Member

@maflcko maflcko commented Apr 27, 2020

A naive implementation of this template is dangerous, because the call site might accidentally omit the template parameter:

template <typename D>
D GetRandDur(const D& duration_max)
{
    return D{GetRand(duration_max.count())};
}

BOOST_AUTO_TEST_CASE(util_time_GetRandTime)
{
    std::chrono::seconds rand_hour = GetRandDur(std::chrono::hours{1});
    // Want seconds to be in range [0..1hour), but always get zero :((((
    BOOST_CHECK_EQUAL(rand_hour.count(), 0);
}

Luckily std::common_type is already specialised in the standard lib for std::chrono::duration (https://en.cppreference.com/w/cpp/chrono/duration/common_type). And its effect seem to be that the call site must always specify the template argument explicitly.

So instead of implementing the function for each duration type by hand, replace it with a templated version that is safe to use.

@maflcko
Copy link
Member Author

maflcko commented Apr 27, 2020

Conceptual suggestion by @amitiuttarwar #18038 (comment)
Approach suggestion by @promag #18751 (review)
Implementation by @sipa #18751 (comment)

@maflcko
Copy link
Member Author

maflcko commented Apr 27, 2020

Appears to be working for me:

error: no matching function for call to 'GetRandDur'
    GetRandDur(std::chrono::hours{1});
    ^~~~~~~~~~
./random.h:74:3: note: candidate template ignored: couldn't infer template argument 'D'
D GetRandDur(typename std::common_type<D>::type max) noexcept
  ^
1 error generated.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK bbbb748.

Did the same locally yesterday.

@maflcko maflcko changed the title Replace GetRandMicros with templated GetRandDur Add templated GetRandDur<> Apr 27, 2020
@maflcko maflcko changed the title Add templated GetRandDur<> Add templated GetRandDuration<> Apr 27, 2020
@maflcko
Copy link
Member Author

maflcko commented Apr 27, 2020

Force pushed to

  • Make diff smaller by 2 lines, but at the same time introduce a new GetRandMillis
  • Address feedback by @jonatack to clarify the function name

@jonatack
Copy link
Member

ACK, could add unit tests to util_tests or random_tests? Only GetRand and GetRandInt seem to have coverage.

@maflcko
Copy link
Member Author

maflcko commented Apr 27, 2020

Done

std::chrono::microseconds GetRandMicros(std::chrono::microseconds duration_max) noexcept;
/** Generate a random duration in the range [0..max). Precondition: max.count() > 0 */
template <typename D>
D GetRandomDuration(typename std::common_type<D>::type max) noexcept
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a comment here to explain why the std::common_type construction is used here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea came from here, btw: https://stackoverflow.com/a/37737627/8342274

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@DrahtBot
Copy link
Contributor

DrahtBot commented Apr 27, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

No conflicts as of last run.

@promag
Copy link
Contributor

promag commented Apr 27, 2020

ACK faf2e85.

src/random.h Outdated
@@ -67,9 +67,21 @@
* Thread-safe.
*/
void GetRandBytes(unsigned char* buf, int num) noexcept;
/** Generate a random integer in the range [0..range). Precondition: range > 0 */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you're changing these comments anyway, you might want to add that the random number comes from a uniform distribution (especially for durations this is not obvious).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed

@laanwj
Copy link
Member

laanwj commented Apr 30, 2020

Code review ACK 0000ea3

@promag
Copy link
Contributor

promag commented Apr 30, 2020

Code review ACK 0000ea3.

@jonatack
Copy link
Member

ACK 0000ea3 thanks for the improved documentation. Code review, built, ran src/test/test_bitcoin -t random_tests -l test_suite for the new unit tests, git diff fa05a4c 0000ea3 since previous review:

diff --git a/src/random.h b/src/random.h
index bb9b8c2a2a..0c6dc24983 100644
--- a/src/random.h
+++ b/src/random.h
@@ -67,9 +67,9 @@
  * Thread-safe.
  */
 void GetRandBytes(unsigned char* buf, int num) noexcept;
-/** Generate a random integer in the range [0..range). Precondition: range > 0 */
+/** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */
 uint64_t GetRand(uint64_t nMax) noexcept;
-/** Generate a random duration in the range [0..max). Precondition: max.count() > 0 */
+/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */

@maflcko maflcko removed the Tests label Apr 30, 2020
@maflcko maflcko closed this Apr 30, 2020
@maflcko maflcko reopened this Apr 30, 2020
@maflcko
Copy link
Member Author

maflcko commented Apr 30, 2020

Open-Close to re-run ci. See #15847 (comment)

@maflcko maflcko closed this Apr 30, 2020
@maflcko maflcko reopened this Apr 30, 2020
@promag
Copy link
Contributor

promag commented May 5, 2020

Merge?

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK.

Comment on lines +70 to 71
/** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */
uint64_t GetRand(uint64_t nMax) noexcept;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */
uint64_t GetRand(uint64_t nMax) noexcept;
/** Generate a uniform random integer in the range [0..nMax). Precondition: nMax > 0 */
uint64_t GetRand(uint64_t nMax) noexcept;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. To not invalidate all the ACKs, I am going to leave this for now.

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 0000ea3 with non-blocking nit.

@maflcko maflcko merged commit 9518708 into bitcoin:master May 15, 2020
@maflcko maflcko deleted the 2004-randDur branch May 15, 2020 13:01
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request May 17, 2020
0000ea3 test: Add test for GetRandMillis and GetRandMicros (MarcoFalke)
fa0e5b8 Add templated GetRandomDuration<> (MarcoFalke)

Pull request description:

  A naive implementation of this template is dangerous, because the call site might accidentally omit the template parameter:

  ```cpp
  template <typename D>
  D GetRandDur(const D& duration_max)
  {
      return D{GetRand(duration_max.count())};
  }

  BOOST_AUTO_TEST_CASE(util_time_GetRandTime)
  {
      std::chrono::seconds rand_hour = GetRandDur(std::chrono::hours{1});
      // Want seconds to be in range [0..1hour), but always get zero :((((
      BOOST_CHECK_EQUAL(rand_hour.count(), 0);
  }
  ```

  Luckily `std::common_type` is already specialised in the standard lib for `std::chrono::duration` (https://en.cppreference.com/w/cpp/chrono/duration/common_type). And its effect seem to be that the call site must always specify the template argument explicitly.

  So instead of implementing the function for each duration type by hand, replace it with a templated version that is safe to use.

ACKs for top commit:
  laanwj:
    Code review ACK 0000ea3
  promag:
    Code review ACK 0000ea3.
  jonatack:
    ACK 0000ea3 thanks for the improved documentation. Code review, built, ran `src/test/test_bitcoin -t random_tests -l test_suite` for the new unit tests, `git diff fa05a4c 0000ea3` since previous review:
  hebasto:
    ACK 0000ea3 with non-blocking [nit](bitcoin#18781 (comment)).

Tree-SHA512: e89d46e31452be6ea14269ecbbb2cdd9ae83b4412cd14dff7d1084283092722a2f847cb501e8054394e4a3eff852f9c87f6d694fd008b3f7e8458cb5a3068af7
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Feb 1, 2021
Summary:
> instead of implementing the function for each duration type by hand, replace it with a templated version that is safe to use.

> A naive implementation of this template is dangerous, because the call site might accidentally omit the template parameter:
>
> ```
>     template <typename D>
>     D GetRandDur(const D& duration_max)
>     {
>         return D{GetRand(duration_max.count())};
>     }
>
>     BOOST_AUTO_TEST_CASE(util_time_GetRandTime)
>     {
>        std::chrono::seconds rand_hour = GetRandDur(std::chrono::hours{1});
>        // Want seconds to be in range [0..1hour), but always get zero :((((
>        BOOST_CHECK_EQUAL(rand_hour.count(), 0);
>     }
> ```
This is a backport of Core [[bitcoin/bitcoin#18781 | PR18781]]

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Differential Revision: https://reviews.bitcoinabc.org/D9118
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants