summaryrefslogtreecommitdiffstats
path: root/doc/developer-guide/identifying-resource-leaks.md
blob: 851fc4424bc2f8674e6637168220ffc5bf2a7426 (plain)
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# Identifying Resource Leaks

Like most other pieces of software, GlusterFS is not perfect in how it manages
its resources like memory, threads and the like. Gluster developers try hard to
prevent leaking resources but releasing and unallocating the used structures.
Unfortunately every now and then some resource leaks are unintentionally added.

This document tries to explain a few helpful tricks to identify resource leaks
so that they can be addressed.


## Debug Builds

There are certain techniques used in GlusterFS that make it difficult to use
tools like Valgrind for memory leak detection. There are some build options
that make it more practical to use Valgrind and other tools. When running
Valgrind, it is important to have GlusterFS builds that contain the
debuginfo/symbols. Some distributions (try to) strip the debuginfo to get
smaller executables. Fedora and RHEL based distributions have sub-packages
called ...-debuginfo that need to be installed for symbol resolving.


### Memory Pools

By using memory pools, there are no allocation/freeing of single structures
needed. This improves performance, but also makes it impossible to track the
allocation and freeing of srtuctures.

It is possible to disable the use of memory pools, and use standard `malloc()`
and `free()` functions provided by the C library. Valgrind is then able to
track the allocated areas and verify if they have been free'd. In order to
disable memory pools, the Gluster sources needs to be configured with the
`--enable-debug` option:

```shell
./configure --enable-debug
```

When building RPMs, the `.spec` handles the `--with=debug` option too:

```shell
make dist
rpmbuild -ta --with=debug glusterfs-....tar.gz
```

### Dynamically Loaded xlators

Valgrind tracks the call chain of functions that do memory allocations. The
addresses of the functions are stored and before Valgrind exits the addresses
are resolved into human readable function names and offsets (line numbers in
source files). Because Gluster loads xlators dynamically, and unloads then
before exiting, Valgrind is not able to resolve the function addresses into
symbols anymore. Whenever this happend, Valgrind shows `???` in the output,
like

```
  ==25170== 344 bytes in 1 blocks are definitely lost in loss record 233 of 324
  ==25170==    at 0x4C29975: calloc (vg_replace_malloc.c:711)
  ==25170==    by 0x52C7C0B: __gf_calloc (mem-pool.c:117)
  ==25170==    by 0x12B0638A: ???
  ==25170==    by 0x528FCE6: __xlator_init (xlator.c:472)
  ==25170==    by 0x528FE16: xlator_init (xlator.c:498)
  ...
```

These `???` can be prevented by not calling `dlclose()` for unloading the
xlator. This will cause a small leak of the handle that was returned with
`dlopen()`, but for improved debugging this can be acceptible. For this and
other Valgrind features, a `--enable-valgrind` option is available to
`./configure`. When GlusterFS is built with this option, Valgrind will be able
to resolve the symbol names of the functions that do memory allocations inside
xlators.

```shell
./configure --enable-valgrind
```

When building RPMs, the `.spec` handles the `--with=valgrind` option too:

```shell
make dist
rpmbuild -ta --with=valgrind glusterfs-....tar.gz
```

## Running Valgrind against a single xlator

Debugging a single xlator is not trivial. But there are some tools to make it
easier. The `sink` xlator does not do any memory allocations itself, but
contains just enough functionality to mount a volume with only the `sink`
xlator. There is a little gfapi application under `tests/basic/gfapi/` in the
GlusterFS sources that can be used to run only gfapi and the core GlusterFS
infrastructure with the `sink` xlator. By extending the `.vol` file to load
more xlators, each xlator can be debugged pretty much separately (as long as
the xlators have no dependencies on each other). A basic Valgrind run with the
suitable configure options looks like this:

```shell
./autogen.sh
./configure --enable-debug --enable-valgrind
make && make install
cd tests/basic/gfapi/
make gfapi-load-volfile
valgrind ./gfapi-load-volfile sink.vol
```

Combined with other very useful options to Valgrind, the following execution
shows many more useful details:

```shell
valgrind \
        --fullpath-after= --leak-check=full --show-leak-kinds=all \
        ./gfapi-load-volfile sink.vol
```

Note that the `--fullpath-after=` option is left empty, this makes Valgrind
print the full path and filename that contains the functions:

```
==2450== 80 bytes in 1 blocks are definitely lost in loss record 8 of 60
==2450==    at 0x4C29975: calloc (/builddir/build/BUILD/valgrind-3.11.0/coregrind/m_replacemalloc/vg_replace_malloc.c:711)
==2450==    by 0x52C6F73: __gf_calloc (/usr/src/debug/glusterfs-3.11dev/libglusterfs/src/mem-pool.c:117)
==2450==    by 0x12F10CDA: init (/usr/src/debug/glusterfs-3.11dev/xlators/meta/src/meta.c:231)
==2450==    by 0x528EFD5: __xlator_init (/usr/src/debug/glusterfs-3.11dev/libglusterfs/src/xlator.c:472)
==2450==    by 0x528F105: xlator_init (/usr/src/debug/glusterfs-3.11dev/libglusterfs/src/xlator.c:498)
==2450==    by 0x52D9D8B: glusterfs_graph_init (/usr/src/debug/glusterfs-3.11dev/libglusterfs/src/graph.c:321)
...
```

In the above example, the `init` function in `xlators/meta/src/meta.c` does a
memory allocation on line 231. This memory is never free'd again, and hence
Valgrind logs this call stack. When looking in the code, it seems that the
allocation of `priv` is assigned to the `this->private` member of the
`xlator_t` structure. Because the allocation is done in `init()`, free'ing is
expected to happen in `fini()`. Both functions are shown below, with the
inclusion of the empty `fini()`:


```
226 int
227 init (xlator_t *this)
228 {
229         meta_priv_t *priv = NULL;
230 
231         priv = GF_CALLOC (sizeof(*priv), 1, gf_meta_mt_priv_t);
232         if (!priv)
233                 return -1;
234 
235         GF_OPTION_INIT ("meta-dir-name", priv->meta_dir_name, str, out);
236 
237         this->private = priv;
238 out:
239         return 0;
240 }
241 
242 
243 int
244 fini (xlator_t *this)
245 {
246         return 0;
247 }
```

In this case, the resource leak can be addressed by adding a single line to the
`fini()` function:

```
243 int
244 fini (xlator_t *this)
245 {
246         GF_FREE (this->private);
247         return 0;
248 }
```

Running the same Valgrind command and comparing the output will show that the
memory leak in `xlators/meta/src/meta.c:init` is not reported anymore.