File: abi-version-policy.md

package info (click to toggle)
opentelemetry-cpp 1.19.0-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 8,744 kB
  • sloc: cpp: 79,029; sh: 1,640; makefile: 43; python: 31
file content (529 lines) | stat: -rw-r--r-- 13,536 bytes parent folder | download | duplicates (3)
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# Application Binary Interface (ABI) version policy

## Goals

### Instrumented applications

Once a given release of opentelemetry-cpp is published as stable,
subsequent releases are expected to provide compatibility,
to avoid disruption.

Compatibility at the source code level (API) is expected,
so that no code change in an application already instrumented is required
to adopt a newer release.

Also, compatibility at the binary level (ABI) is expected,
so that an instrumented application already compiled against
opentelemetry-cpp API headers from an older version, and distributed as a
binary package, can be linked against the SDK library from a newer version.

In other words, once an application is instrumented using the
opentelemetry-cpp API, adopting a newer version:

* should not require source code changes,
* should not require building and distributing a new package.

### opentelemetry-cpp

The opentelemetry-cpp project itself needs to deliver fixes and features on
a continual basis.

Reasons to change an API can be external:

* new APIs added in the specification, for new features
* changes in APIs in the specifications, extending existing features.

Changes can also be caused by internal issues:

* fix technical design issues with an API (incorrect types used, missing
  parameters)

Regardless of the root cause for a change (bug or feature),
changes to the existing APIs are unavoidable.

For the opentelemetry-cpp project to stay healthy,
there must be a way to deliver ABI breaking fixes while preserving
compatibility for users.

This is achieved with ABI versions.

## Concept of ABI version

### Inline namespaces

For the sake of illustration, let's consider a fictitious API such as:

```cpp
namespace opentelemetry
{
  namespace common
  {
    class OtelUtil
    {
      virtual void DoSomething() = 0;
    };
  }
}
```

An application can be instrumented to use it:

```cpp
opentelemetry::common::OtelUtil *p = ...;
p->DoSomething();
```

The problem here is that the binary package produced during the build
contains references to symbols such as:

```cpp
opentelemetry::common::OtelUtil::DoSomething()
```

Because all symbols are in the same `opentelemetry::` namespace, it becomes
impossible to deliver changes (new or different symbols) while at the same
time not deliver changes (to preserve binary compatibility).

This is resolved by the use of inline namespaces in C++, leading to
dedicated ABI versions.

Revised example:

```cpp
namespace opentelemetry
{
  inline namespace v1
  {
    namespace common
    {
      class OtelUtil
      {
        virtual void DoSomething() = 0;
      };
    }
  }
}
```

```cpp
opentelemetry::common::OtelUtil *p = ...;
p->DoSomething();
```

When building, the compiler translates `opentelemetry::common` to
`opentelemetry::v1::common`

The symbols delivered by the opentelemetry library, and used by the
instrumented application, are:

```cpp
opentelemetry::v1::common::OtelUtil::DoSomething()
```

With the help of the `OPENTELEMETRY_BEGIN_NAMESPACE` macro,
the source code can be abstracted to:

```cpp
#define OPENTELEMETRY_ABI_VERSION_NO 1

OPENTELEMETRY_BEGIN_NAMESPACE
{
  namespace common
  {
    class OtelUtil
    {
      virtual void DoSomething() = 0;
    };
  }
}
OPENTELEMETRY_END_NAMESPACE
```

Adding a new API to the OtelUtil class is an ABI breaking change (the C++
virtual table is different).

This change can be delivered, but in a different namespace, which defines
a different (extended) ABI:

```cpp
#define OPENTELEMETRY_ABI_VERSION_NO 2

OPENTELEMETRY_BEGIN_NAMESPACE
{
  namespace common
  {
    class OtelUtil
    {
      virtual void DoSomething() = 0;
      virtual void DoSomethingMore() = 0;
    };
  }
}
OPENTELEMETRY_END_NAMESPACE
```

Compiling this declaration produce the following symbols:

```cpp
opentelemetry::v2::common::OtelUtil::DoSomething()
opentelemetry::v2::common::OtelUtil::DoSomethingMore()
```

### ABI version

By defining an inline namespace per ABI, it is possible to deliver
an ABI v2 implementation independently of ABI v1, keeping v1 unchanged.

Code compiled against the v1 ABI can continue to link against a library
providing v1 symbols, while code compiled against the v2 ABI can link
against a library providing the v2 symbols.

An application, when building, can choose to:

* either build against the v1 interface
* or build against the v2 interface

Using v1 ensures compatibility, at the API and ABI level.

Using v2 allows to benefit from new features.

The choice is made by the application owner when building,
and not by the opentelemetry-cpp library.

This, in essence, describes the technical 'ABI version' building block used
to deliver breaking changes.

How to use this versioning feature, provided by the C++ language with inline
namespaces, is described in the next section.

## Versioning policy

### Version scope

Due to dependencies between classes, having an ABI version per class is not
viable.

For example, class TracerProvider depends on
class Tracer, as it builds tracers.

If class Tracer comes in multiple versions,
then class TracerProvider needs to also comes in multiple versions,
and these versions are correlated:

* v1::TracerProvider creates v1::Tracer instances
* v2::TracerProvider creates v2::Tracer instances

The next logical scope is to consider ABI versions per signal.

This is not viable either, because of interdependencies between signals:

* metrics can use traces in examplars
* eventually, the trace implementation can emit internal metrics
* all signals can use common utility classes

In conclusion, the scope of a version is the entire opentelemetry-cpp project.

### Source code

The number of breaking change that affects an APIs is expected to be very low,
with only specific methods affected directly.

It is very undesirable to:

* create a different git branch per ABI version, in effect forking the
  entire code base
* create a different header file per ABI version, in effect forking
  the entire include headers

just to handle a few modified APIs.

As a result, differences between ABI versions are handled using C
preprocessor macros, in the few places where it is necessary.

For example:

```cpp
OPENTELEMETRY_BEGIN_NAMESPACE
{
  namespace common
  {
    class OtelUtil
    {
      virtual void DoSomething() = 0;

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
      // Added in ABI v2
      virtual void DoSomethingMore() = 0;
#endif

    };
  }
}
OPENTELEMETRY_END_NAMESPACE
```

The same source code, when compiled with different values of
OPENTELEMETRY_ABI_VERSION_NO, produces the proper declarations for ABI v1 or
ABI v2.

This solution reduces the maintenance burden on the source code itself.

## Versions lifecycle

For a given ABI, the lifecycle is:

* EXPERIMENTAL
* STABLE
* DEPRECATED
* REMOVED

In the EXPERIMENTAL status,
any change to the ABI can be implemented, without notice.

There are no compatibility guarantees.
This status is meant as a preview, until the ABI is declared STABLE.

In the STABLE status,
changes to the ABI are forbidden, to guarantee stability.

In the DEPRECATED status, the ABI is still functional and supported,
but instrumented applications are encouraged to migrate to a newer ABI.

In the REMOVED status,
the given ABI is no longer available.

The following sections describe the migration path from one ABI (v1) to the
next (v2).

### STABLE V1

In this state, only one ABI version is available, and it is closed to
changes.

Instrumented applications are built against ABI v1 by default.

opentelemetry-cpp produces a library for ABI v1 by default.

Fixes introducing breaking changes can __not__ be delivered.

This is the current status as of opentelemetry-cpp version 1.11.0

### STABLE V1, EXPERIMENTAL V2

In this state, two ABI versions are available.

CMake offers the following options:

```cmake
option(WITH_ABI_VERSION_1 "ABI version 1" ON)
option(WITH_ABI_VERSION_2 "EXPERIMENTAL: ABI version 2 preview" OFF)
```

Instrumented applications are built against ABI v1 by default,
but may ask to use ABI v2 instead.

opentelemetry-cpp produces a library for stable ABI v1 by default,
but can be configured to provide experimental ABI v2 instead.

Fixes introducing breaking changes can only be delivered in the experimental
ABI v2.

### STABLE V1, STABLE V2, EXPERIMENTAL V3

In this state, two stable ABI versions are available,
the ABI offered by default is the conservative ABI v1.

Fixes introducing breaking changes can no longer be delivered in ABI v2,
because it is declared stable.
An experimental ABI v3 is created.

CMake offers the following options:

```cmake
option(WITH_ABI_VERSION_1 "ABI version 1" ON)
option(WITH_ABI_VERSION_2 "ABI version 2" OFF)
option(WITH_ABI_VERSION_3 "EXPERIMENTAL: ABI version 3 preview" OFF)
```

Instrumented applications are built against stable ABI v1 by default,
but may ask to use the now stable ABI v2 instead.

opentelemetry-cpp produces a library for ABI v1 by default,
but can be configured to provide ABI v2 instead.

Fixes introducing breaking changes can only be delivered in the experimental
ABI v3.

### DEPRECATED V1, STABLE V2, EXPERIMENTAL V3

In this state, two stable ABI versions are available,
the ABI offered by default is the newer ABI v2.

CMake offers the following options:

```cmake
option(WITH_ABI_VERSION_1 "DEPRECATED: ABI version 1" OFF)
option(WITH_ABI_VERSION_2 "ABI version 2" ON)
option(WITH_ABI_VERSION_3 "EXPERIMENTAL: ABI version 3 preview" OFF)
```

Instrumented applications are built against stable ABI v2 by default,
but may ask to use stable ABI v1 instead.

opentelemetry-cpp produces a library for ABI v2 by default,
but can be configured to provide ABI v1 instead.

Fixes introducing breaking changes can only be delivered in the experimental
ABI v3.

### (REMOVED V1), STABLE V2, EXPERIMENTAL V3

In this state, the only stable ABI available is v2.
ABI v1 is no longer supported.

CMake offers the following options:

```cmake
option(WITH_ABI_VERSION_2 "ABI version 2" ON)
option(WITH_ABI_VERSION_3 "EXPERIMENTAL: ABI version 3 preview" OFF)
```

Instrumented applications and the opentelemetry-cpp library are build using
ABI v2.

Now that ABI v1 no longer exists, the code:

```cpp
OPENTELEMETRY_BEGIN_NAMESPACE
{
  namespace common
  {
    class OtelUtil
    {
      virtual void DoSomething() = 0;

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
      // Added in ABI v2
      virtual void DoSomethingMore() = 0;
#endif

    };
  }
}
OPENTELEMETRY_END_NAMESPACE
```

can be simplified to:

```cpp
OPENTELEMETRY_BEGIN_NAMESPACE
{
  namespace common
  {
    class OtelUtil
    {
      virtual void DoSomething() = 0;
      virtual void DoSomethingMore() = 0;
    };
  }
}
OPENTELEMETRY_END_NAMESPACE
```

## Practical Example

### Fixing issue #2033

The problem is to change the MeterProvider::GetMeter() prototype,
to follow specification changes.

See the issue description for all details:

* [Metrics API/SDK]
  Add InstrumentationScope attributes in MeterProvider::GetMeter() #2033

#### API change

In the API, class MeterProvider is changed as follows:

```cpp
class MeterProvider
{
public:
  virtual ~MeterProvider() = default;
  /**
   * Gets or creates a named Meter instance.
   *
   * Optionally a version can be passed to create a named and versioned Meter
   * instance.
   */
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
  virtual nostd::shared_ptr<Meter> GetMeter(nostd::string_view library_name,
                                            nostd::string_view library_version = "",
                                            nostd::string_view schema_url = "",
                                            const common::KeyValueIterable *attributes = nullptr) noexcept = 0;
#else
  virtual nostd::shared_ptr<Meter> GetMeter(nostd::string_view library_name,
                                            nostd::string_view library_version = "",
                                            nostd::string_view schema_url      = "") noexcept = 0;
#endif

  /* ... */
};

```

Note how the ABI changes, while the API stays compatible, requiring no code
change in the caller when providing up to 3 parameters.

#### SDK change

In the SDK class declaration, implement the expected API.

```cpp
class MeterProvider final : public opentelemetry::metrics::MeterProvider
{
public:

  /* ... */

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
  nostd::shared_ptr<opentelemetry::metrics::Meter> GetMeter(
      nostd::string_view name,
      nostd::string_view version                                = "",
      nostd::string_view schema_url                             = "",
      const opentelemetry::common::KeyValueIterable *attributes = nullptr) noexcept override;
#else
  nostd::shared_ptr<opentelemetry::metrics::Meter> GetMeter(
      nostd::string_view name,
      nostd::string_view version    = "",
      nostd::string_view schema_url = "") noexcept override;
#endif

  /* ... */
};
```

In the SDK implementation:

* either get the new parameters from the extended ABI v2 method
* or provide default values for the old ABI v1 method

```cpp
nostd::shared_ptr<metrics_api::Meter> MeterProvider::GetMeter(
    nostd::string_view name,
    nostd::string_view version,
    nostd::string_view schema_url
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
    ,
    const opentelemetry::common::KeyValueIterable *attributes
#endif
    ) noexcept
{
#if OPENTELEMETRY_ABI_VERSION_NO < 2
  const opentelemetry::common::KeyValueIterable *attributes = nullptr;
#endif

  /* common implementation, use attributes */
}
```