LCOV - code coverage report
Current view: top level - snapdev - unique_number.h (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 24 24
Test Date: 2025-08-24 15:46:23 Functions: 100.0 % 5 5
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright (c) 2011-2025  Made to Order Software Corp.  All Rights Reserved
       2              : //
       3              : // https://snapwebsites.org/project/snapdev
       4              : // contact@m2osw.com
       5              : //
       6              : // This program is free software: you can redistribute it and/or modify
       7              : // it under the terms of the GNU General Public License as published by
       8              : // the Free Software Foundation, either version 3 of the License, or
       9              : // (at your option) any later version.
      10              : //
      11              : // This program is distributed in the hope that it will be useful,
      12              : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13              : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14              : // GNU General Public License for more details.
      15              : //
      16              : // You should have received a copy of the GNU General Public License
      17              : // along with this program.  If not, see <https://www.gnu.org/licenses/>.
      18              : #pragma once
      19              : 
      20              : /** \file
      21              :  * \brief Retrieve the next unique number.
      22              :  *
      23              :  * The unique_number function reads a file for a unique number. That number
      24              :  * is managed by this function. The file is locked so any number of threads
      25              :  * and processes can call the function safely.
      26              :  *
      27              :  * The number is managed as a 128 bits number. You may cast it down to any
      28              :  * number of bits as required by your function.
      29              :  *
      30              :  * The function also accepts an index which gives you the ability to create
      31              :  * any number of counters within one file. This is particularly useful if
      32              :  * your service makes use of many different counter. Instead of create a
      33              :  * separate file for each counter, reuse the same file with varying
      34              :  * indexes. This is likely faster since the kernel only needs to cache one
      35              :  * single file.
      36              :  */
      37              : 
      38              : // libexcept
      39              : //
      40              : #include    <libexcept/exception.h>
      41              : 
      42              : 
      43              : // snapdev
      44              : //
      45              : #include    <snapdev/lockfile.h>
      46              : #include    <snapdev/raii_generic_deleter.h>
      47              : 
      48              : 
      49              : // C
      50              : //
      51              : #include    <sys/file.h>
      52              : //#include    <unistd.h>
      53              : //#include    <string.h>
      54              : 
      55              : 
      56              : 
      57              : namespace snapdev
      58              : {
      59              : 
      60          202 : DECLARE_MAIN_EXCEPTION(unique_number_error);
      61              : 
      62            1 : DECLARE_EXCEPTION(unique_number_error, io_error);
      63          200 : DECLARE_EXCEPTION(unique_number_error, out_of_range);
      64            1 : DECLARE_EXCEPTION(unique_number_error, path_missing);
      65              : 
      66              : 
      67              : 
      68              : /** \brief Limit the size of one counter file to 1Mb.
      69              :  *
      70              :  * This is the limit of the index passed to the unique_number(). If you
      71              :  * pass a negative number of a number equal or larger to this number,
      72              :  * then the function raises an error.
      73              :  */
      74              : constexpr int const COUNTER_MAXIMUM_INDEX = 65536;
      75              : 
      76              : 
      77              : /** \brief Manage a unique number within a file.
      78              :  *
      79              :  * This function handles a unique number. It creates a file if it
      80              :  * does not exist yet and starts counting. Each time you call the
      81              :  * function, it returns the current counter value plus 1. The very
      82              :  * first time, it returns 1. The counter is 128 bits so rather
      83              :  * unlikely to wrap around.
      84              :  *
      85              :  * The \p counter parameter holds the path and filename to the
      86              :  * counter which gets saved to file.
      87              :  *
      88              :  * The \p index parameter can be set to a number larger than 0 to
      89              :  * reference a different counter. That way, you can make use of the
      90              :  * same file for many counters, which is likely going to be faster
      91              :  * and reduce the amount of disk space necessary to manage the counters.
      92              :  *
      93              :  * The index cannot be negative or larger or equal to COUNTER_MAXIMUM_INDEX.
      94              :  *
      95              :  * For a specific counter, the number returned is unique (assuming no
      96              :  * wrapping occurs) for the computer it exists on. To make it
      97              :  * unique in a cluster, consider adding the computer host name. Similarly,
      98              :  * to make it unique anywhere, also add the cluster unique identifier.
      99              :  *
     100              :  * \note
     101              :  * The index can be used to create counters at any index. Intermediate
     102              :  * counters do not need to exist. So if you use 100, it will create a
     103              :  * counter at offset 100 * 16 (unsigned __int128 is 16 bytes). This
     104              :  * means the file may end up being a sparse file.
     105              :  *
     106              :  * \exception path_missing
     107              :  * This exception is raised if the \p counter path is an empty string.
     108              :  *
     109              :  * \exception out_of_range
     110              :  * This exception is raised if the index is negative or larger or
     111              :  * equal to the limit, COUNTER_MAXIMUM_INDEX.
     112              :  *
     113              :  * \exception io_error
     114              :  * This exception is raised if an I/O error is detected. This includes
     115              :  * opening, seeking, reading, and writing to the file.
     116              :  *
     117              :  * \param[in] counter  The path to the counter file.
     118              :  * \param[in] index  The counter index starting at 0.
     119              :  *
     120              :  * \return The current counter after 1 was added to it.
     121              :  */
     122              : #pragma GCC diagnostic push
     123              : #pragma GCC diagnostic ignored "-Wpedantic"
     124        10302 : inline unsigned __int128 unique_number(std::string const & counter, int index = 0)
     125              : {
     126        10302 :     if(counter.empty())
     127              :     {
     128            3 :         throw path_missing("a counter filename must be specified when calling snapdev::unique_number.");
     129              :     }
     130        10301 :     if(static_cast<std::size_t>(index) >= static_cast<std::size_t>(COUNTER_MAXIMUM_INDEX))
     131              :     {
     132              :         throw out_of_range(
     133              :                   "counter index in unique_number must be defined between 0 and "
     134          400 :                 + std::to_string(COUNTER_MAXIMUM_INDEX - 1)
     135          600 :                 + " inclusive.");
     136              :     }
     137              : 
     138        10101 :     raii_fd_t fd(::open(counter.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH));
     139        10101 :     if(fd == nullptr)
     140              :     {
     141            1 :         throw io_error("could not open unique_number file \"" + counter + "\".");
     142              :     }
     143              : 
     144              :     // make sure we are exclusive
     145              :     //
     146        10100 :     lockfd lock(fd.get(), operation_t::OPERATION_EXCLUSIVE);
     147              : 
     148        10100 :     unsigned __int128 result(0);
     149        10100 :     off_t const position(index * sizeof(result));
     150        10100 :     if(lseek(fd.get(), position, SEEK_SET) != position)
     151              :     {
     152              :         throw io_error("could not properly lseek() unique_number file \"" + counter + "\" to read the counter."); // LCOV_EXCL_LINE
     153              :     }
     154              : 
     155              :     // read errors are expected when calling the function for the first time
     156              :     //
     157        10100 :     if(read(fd.get(), &result, sizeof(result)) != sizeof(result))
     158              :     {
     159            4 :         result = 0;
     160              :     }
     161              : 
     162        10100 :     ++result;
     163              : 
     164        10100 :     if(lseek(fd.get(), position, SEEK_SET) != position)
     165              :     {
     166              :         throw io_error("could not properly lseek() unique_number file \"" + counter + "\" to write the counter."); // LCOV_EXCL_LINE
     167              :     }
     168        10100 :     if(write(fd.get(), &result, sizeof(result)) != sizeof(result))
     169              :     {
     170              :         throw io_error("could not properly save() unique_number to file \"" + counter + "\"."); // LCOV_EXCL_LINE
     171              :     }
     172              : 
     173        10100 :     return result;
     174        10101 : }
     175              : #pragma GCC diagnostic pop
     176              : 
     177              : 
     178              : 
     179              : } // namespace snapdev
     180              : // vim: ts=4 sw=4 et
        

Generated by: LCOV version 2.0-1

Snap C++ | List of projects | List of versions