aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/clickhouse/src/IO/OpenedFileCache.h
blob: 2cecc675af72c7c8cb3e7baedacd67c1fec7fd30 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#pragma once

#include <map>
#include <mutex>

#include <Core/Types.h>
#include <IO/OpenedFile.h>
#include <Common/ElapsedTimeProfileEventIncrement.h>
#include <Common/ProfileEvents.h>

#include <city.h>


namespace ProfileEvents
{
    extern const Event OpenedFileCacheHits;
    extern const Event OpenedFileCacheMisses;
    extern const Event OpenedFileCacheMicroseconds;
}

namespace DB
{


/** Cache of opened files for reading.
  * It allows to share file descriptors when doing reading with 'pread' syscalls on readonly files.
  * Note: open/close of files is very cheap on Linux and we should not bother doing it 10 000 times a second.
  * (This may not be the case on Windows with WSL. This is also not the case if strace is active. Neither when some eBPF is loaded).
  * But sometimes we may end up opening one file multiple times, that increases chance exhausting opened files limit.
  */
class OpenedFileCache
{
    class OpenedFileMap
    {
        using Key = std::pair<std::string /* path */, int /* flags */>;

        using OpenedFileWeakPtr = std::weak_ptr<OpenedFile>;
        using Files = std::map<Key, OpenedFileWeakPtr>;

        Files files;
        std::mutex mutex;

    public:
        using OpenedFilePtr = std::shared_ptr<OpenedFile>;

        OpenedFilePtr get(const std::string & path, int flags)
        {
            Key key(path, flags);

            std::lock_guard lock(mutex);

            auto [it, inserted] = files.emplace(key, OpenedFilePtr{});
            if (!inserted)
            {
                if (auto res = it->second.lock())
                {
                    ProfileEvents::increment(ProfileEvents::OpenedFileCacheHits);
                    return res;
                }
            }
            ProfileEvents::increment(ProfileEvents::OpenedFileCacheMisses);

            OpenedFilePtr res
            {
                new OpenedFile(path, flags),
                [key, this](auto ptr)
                {
                    {
                        std::lock_guard another_lock(mutex);
                        files.erase(key);
                    }
                    delete ptr;
                }
            };

            it->second = res;
            return res;
        }

        void remove(const std::string & path, int flags)
        {
            Key key(path, flags);
            std::lock_guard lock(mutex);
            files.erase(key);
        }
    };

    static constexpr size_t buckets = 1024;
    std::vector<OpenedFileMap> impls{buckets};

public:
    using OpenedFilePtr = OpenedFileMap::OpenedFilePtr;

    OpenedFilePtr get(const std::string & path, int flags)
    {
        ProfileEventTimeIncrement<Microseconds> watch(ProfileEvents::OpenedFileCacheMicroseconds);
        const auto bucket = CityHash_v1_0_2::CityHash64(path.data(), path.length()) % buckets;
        return impls[bucket].get(path, flags);
    }

    void remove(const std::string & path, int flags)
    {
        ProfileEventTimeIncrement<Microseconds> watch(ProfileEvents::OpenedFileCacheMicroseconds);
        const auto bucket = CityHash_v1_0_2::CityHash64(path.data(), path.length()) % buckets;
        impls[bucket].remove(path, flags);
    }

    static OpenedFileCache & instance()
    {
        static OpenedFileCache res;
        return res;
    }
};

using OpenedFileCachePtr = std::shared_ptr<OpenedFileCache>;
}