From 17220538b1cd2c9651d628e41857d44fb30f06be Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 30 Apr 2024 10:19:30 +0200 Subject: [PATCH 01/20] Clarified approach on how to extend SDK plugin interfaces --- specification/versioning-and-stability.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/specification/versioning-and-stability.md b/specification/versioning-and-stability.md index 07a545afb5c..aa83514d07c 100644 --- a/specification/versioning-and-stability.md +++ b/specification/versioning-and-stability.md @@ -131,6 +131,12 @@ There are two categories of public features: **plugin interfaces** and **constru Examples of plugins include the SpanProcessor, Exporter, and Sampler interfaces. Examples of constructors include configuration objects, environment variables, and SDK builders. +**plugin interfaces** may be extended with new functions without requiring a new major version. +Languages which support adding methods to interfaces in a non-breaking manner (e.g. with default implementations) can directly extend the existing interfaces. +Languages which don't support this will have to introduce a new interface which adds the new method(s). +This new interface needs to be accepted in addition to the old one in all places where the previous revision of that plugin interface was accepted. +When the major version of the SDK is bumped, the interfaces SHOULD be consolidated back to a single interface. + Languages which ship binary artifacts SHOULD offer [ABI compatibility](glossary.md#abi-compatibility) for SDK packages. #### Contrib Stability From a4b411a7fa57b5172c9f63d4c4b058e095ef33ff Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 30 Apr 2024 10:29:57 +0200 Subject: [PATCH 02/20] Added onEnding method to SpanProcessor --- specification/trace/sdk.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index d9d58aaedd9..4ea36c27b36 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -38,6 +38,7 @@ linkTitle: SDK - [Span processor](#span-processor) * [Interface definition](#interface-definition) + [OnStart](#onstart) + + [OnEnding](#onendingspan) + [OnEnd(Span)](#onendspan) + [Shutdown()](#shutdown) + [ForceFlush()](#forceflush) @@ -580,6 +581,19 @@ exceptions. **Returns:** `Void` +#### OnEnding + +`OnEnding` is called just before a span is ended. The span is still mutable, but the end timestamp is already set. +This method MUST be called synchronously within the [`Span.End()` API](api.md#end), +therefore it should not block or throw an exception. + +**Parameters:** + +* `span` - a [read/write span object](#additional-span-interfaces) for the span which is about to be ended. + +**Returns:** `Void` + + #### OnEnd(Span) `OnEnd` is called after a span is ended (i.e., the end timestamp is already set). From 600c721dd483bd5f7e27d7c379cd22a524af04b4 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 30 Apr 2024 10:32:33 +0200 Subject: [PATCH 03/20] wording --- specification/versioning-and-stability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/versioning-and-stability.md b/specification/versioning-and-stability.md index aa83514d07c..18c682b7b23 100644 --- a/specification/versioning-and-stability.md +++ b/specification/versioning-and-stability.md @@ -135,7 +135,7 @@ Examples of constructors include configuration objects, environment variables, a Languages which support adding methods to interfaces in a non-breaking manner (e.g. with default implementations) can directly extend the existing interfaces. Languages which don't support this will have to introduce a new interface which adds the new method(s). This new interface needs to be accepted in addition to the old one in all places where the previous revision of that plugin interface was accepted. -When the major version of the SDK is bumped, the interfaces SHOULD be consolidated back to a single interface. +When the major version of the SDK is bumped, the interfaces SHOULD be merged back to a single interface. Languages which ship binary artifacts SHOULD offer [ABI compatibility](glossary.md#abi-compatibility) for SDK packages. From 1d3543d22cc26e5e8b7f13b87edc93279fac4221 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 30 Apr 2024 10:56:34 +0200 Subject: [PATCH 04/20] Added changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9218fe4caf4..fa9c6d6dc43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ release. ### Traces +- Add `OnEnding` callback to SDK `SpanProcessor` interface + ([#4024](https://github.com/open-telemetry/opentelemetry-specification/pull/4024)) + ### Metrics - Change the exemplar behavior to be on by default. From d60594f0c59b6dacf39fdadb16b035a14b477ff2 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 30 Apr 2024 11:00:15 +0200 Subject: [PATCH 05/20] linting --- specification/trace/sdk.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 4ea36c27b36..be7e3d76629 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -38,7 +38,7 @@ linkTitle: SDK - [Span processor](#span-processor) * [Interface definition](#interface-definition) + [OnStart](#onstart) - + [OnEnding](#onendingspan) + + [OnEnding](#onending) + [OnEnd(Span)](#onendspan) + [Shutdown()](#shutdown) + [ForceFlush()](#forceflush) @@ -593,7 +593,6 @@ therefore it should not block or throw an exception. **Returns:** `Void` - #### OnEnd(Span) `OnEnd` is called after a span is ended (i.e., the end timestamp is already set). From 6bf8266158eef5a91cac8ca144364b4092c86d2c Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 30 Apr 2024 11:06:02 +0200 Subject: [PATCH 06/20] more linting --- specification/versioning-and-stability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/versioning-and-stability.md b/specification/versioning-and-stability.md index 18c682b7b23..0cdb575eb04 100644 --- a/specification/versioning-and-stability.md +++ b/specification/versioning-and-stability.md @@ -132,7 +132,7 @@ Examples of plugins include the SpanProcessor, Exporter, and Sampler interfaces. Examples of constructors include configuration objects, environment variables, and SDK builders. **plugin interfaces** may be extended with new functions without requiring a new major version. -Languages which support adding methods to interfaces in a non-breaking manner (e.g. with default implementations) can directly extend the existing interfaces. +Languages which support adding methods to interfaces in a non-breaking manner (e.g. with default implementations) can directly extend the existing interfaces. Languages which don't support this will have to introduce a new interface which adds the new method(s). This new interface needs to be accepted in addition to the old one in all places where the previous revision of that plugin interface was accepted. When the major version of the SDK is bumped, the interfaces SHOULD be merged back to a single interface. From 19d5a77747dd273abe152b878a015a1da62d27b4 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Thu, 2 May 2024 12:13:30 +0200 Subject: [PATCH 07/20] Add experimental status Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com> --- specification/trace/sdk.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index be7e3d76629..5778b9063da 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -583,6 +583,8 @@ exceptions. #### OnEnding +**Status**: [Experimental](../document-status.md) + `OnEnding` is called just before a span is ended. The span is still mutable, but the end timestamp is already set. This method MUST be called synchronously within the [`Span.End()` API](api.md#end), therefore it should not block or throw an exception. From 598a9eb88075df4f063d24bc862f2a2b959912e8 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Thu, 2 May 2024 12:17:28 +0200 Subject: [PATCH 08/20] Removed paragraph about SDK interface extensions --- specification/versioning-and-stability.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/specification/versioning-and-stability.md b/specification/versioning-and-stability.md index 0cdb575eb04..07a545afb5c 100644 --- a/specification/versioning-and-stability.md +++ b/specification/versioning-and-stability.md @@ -131,12 +131,6 @@ There are two categories of public features: **plugin interfaces** and **constru Examples of plugins include the SpanProcessor, Exporter, and Sampler interfaces. Examples of constructors include configuration objects, environment variables, and SDK builders. -**plugin interfaces** may be extended with new functions without requiring a new major version. -Languages which support adding methods to interfaces in a non-breaking manner (e.g. with default implementations) can directly extend the existing interfaces. -Languages which don't support this will have to introduce a new interface which adds the new method(s). -This new interface needs to be accepted in addition to the old one in all places where the previous revision of that plugin interface was accepted. -When the major version of the SDK is bumped, the interfaces SHOULD be merged back to a single interface. - Languages which ship binary artifacts SHOULD offer [ABI compatibility](glossary.md#abi-compatibility) for SDK packages. #### Contrib Stability From 81bef4469d6ea2cab49a6dcb7398066a3bcf9303 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 8 May 2024 14:21:22 +0200 Subject: [PATCH 09/20] Remove accidental merge leftover --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39be8bb6e69..7daa3bb4f5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ release. ### Traces -======= - Links with invalid SpanContext are recorded. ([#3928](https://github.com/open-telemetry/opentelemetry-specification/pull/3928)) - Add `OnEnding` callback to SDK `SpanProcessor` interface From b56fbb92645c9e4546682eba72a2336bf4592148 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 14 May 2024 12:26:02 +0200 Subject: [PATCH 10/20] Disallow concurrent updates, specify invocation order --- specification/trace/sdk.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 5778b9063da..40abdda38fd 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -566,7 +566,8 @@ in the SDK: `OnStart` is called when a span is started. This method is called synchronously on the thread that started the span, therefore it should not block or throw -exceptions. +exceptions. If multiple `SpanProcessors` are registered, their `OnStart` callbacks +are invoked in the order they have been registered. **Parameters:** @@ -588,6 +589,11 @@ exceptions. `OnEnding` is called just before a span is ended. The span is still mutable, but the end timestamp is already set. This method MUST be called synchronously within the [`Span.End()` API](api.md#end), therefore it should not block or throw an exception. +If multiple `SpanProcessors` are registered, their `OnEnding` callbacks +are invoked in the order they have been registered. +The SDK MUST guarantee that the span cannot be modified anymore by any other thread +before invoking `OnEnding` of the first `SpanProcessor`. From that point on, modifications +are only allowed synchronously from within the invoked `OnEnding` callbacks. **Parameters:** From 990c91a80564de98437e96c40a67d27efa66323d Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 14 May 2024 12:31:11 +0200 Subject: [PATCH 11/20] Linting --- specification/trace/sdk.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 40abdda38fd..6b3d11b7350 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -566,7 +566,7 @@ in the SDK: `OnStart` is called when a span is started. This method is called synchronously on the thread that started the span, therefore it should not block or throw -exceptions. If multiple `SpanProcessors` are registered, their `OnStart` callbacks +exceptions. If multiple `SpanProcessors` are registered, their `OnStart` callbacks are invoked in the order they have been registered. **Parameters:** @@ -589,7 +589,7 @@ are invoked in the order they have been registered. `OnEnding` is called just before a span is ended. The span is still mutable, but the end timestamp is already set. This method MUST be called synchronously within the [`Span.End()` API](api.md#end), therefore it should not block or throw an exception. -If multiple `SpanProcessors` are registered, their `OnEnding` callbacks +If multiple `SpanProcessors` are registered, their `OnEnding` callbacks are invoked in the order they have been registered. The SDK MUST guarantee that the span cannot be modified anymore by any other thread before invoking `OnEnding` of the first `SpanProcessor`. From that point on, modifications From 2cf024a8497eb24566b020e59123d148cd79bc34 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Thu, 23 May 2024 09:17:48 +0200 Subject: [PATCH 12/20] Update specification/trace/sdk.md Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com> --- specification/trace/sdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 6b3d11b7350..46c0c616c24 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -591,7 +591,7 @@ This method MUST be called synchronously within the [`Span.End()` API](api.md#en therefore it should not block or throw an exception. If multiple `SpanProcessors` are registered, their `OnEnding` callbacks are invoked in the order they have been registered. -The SDK MUST guarantee that the span cannot be modified anymore by any other thread +The SDK MUST guarantee that the span can no longer be modified by any other thread before invoking `OnEnding` of the first `SpanProcessor`. From that point on, modifications are only allowed synchronously from within the invoked `OnEnding` callbacks. From ae572ca08d0324e675e3c463d6ba80d2c366f4b6 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Fri, 24 May 2024 13:19:52 +0200 Subject: [PATCH 13/20] Apply suggestions from code review Co-authored-by: Joshua MacDonald --- specification/trace/sdk.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 46c0c616c24..7d752e6fadb 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -586,14 +586,14 @@ are invoked in the order they have been registered. **Status**: [Experimental](../document-status.md) -`OnEnding` is called just before a span is ended. The span is still mutable, but the end timestamp is already set. +`OnEnding` is called during the span `End()` operation, after the end timestamp has been set. The Span object is still mutable (i.e., `SetAttribute`, `AddLink`, `AddEvent` can be called) while `OnEnding` is called. This method MUST be called synchronously within the [`Span.End()` API](api.md#end), therefore it should not block or throw an exception. If multiple `SpanProcessors` are registered, their `OnEnding` callbacks are invoked in the order they have been registered. The SDK MUST guarantee that the span can no longer be modified by any other thread before invoking `OnEnding` of the first `SpanProcessor`. From that point on, modifications -are only allowed synchronously from within the invoked `OnEnding` callbacks. +are only allowed synchronously from within the invoked `OnEnding` callbacks. All registered SpanProcessor `OnEnding` callbacks are executed before any SpanProcessor's `OnEnd` callback is invoked. **Parameters:** From 218783fbd1a3b2927691072bf559e62266cf4195 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Jun 2024 16:17:26 +0200 Subject: [PATCH 14/20] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert PajÄ…k --- CHANGELOG.md | 2 +- specification/trace/sdk.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b4e1c54e3..b3459dfd99e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ release. - Clarify the trace SDK should log discarded events and links. ([#4064](https://github.com/open-telemetry/opentelemetry-specification/pull/4064)) -- Add `OnEnding` callback to SDK `SpanProcessor` interface +- Add in-development `OnEnding` callback to SDK `SpanProcessor` interface. ([#4024](https://github.com/open-telemetry/opentelemetry-specification/pull/4024)) ### Metrics diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 69b331a251c..70f0984ed48 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -592,7 +592,7 @@ are invoked in the order they have been registered. #### OnEnding -**Status**: [Experimental](../document-status.md) +**Status**: [Development](../document-status.md) `OnEnding` is called during the span `End()` operation, after the end timestamp has been set. The Span object is still mutable (i.e., `SetAttribute`, `AddLink`, `AddEvent` can be called) while `OnEnding` is called. This method MUST be called synchronously within the [`Span.End()` API](api.md#end), From 1e5c7fa76c5915309ae93457094702040344be19 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 19 Jun 2024 10:15:21 +0200 Subject: [PATCH 15/20] Fix changelog after merge --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1ec6bfa6f7..ac04bbebef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ release. ### Traces +- Add in-development `OnEnding` callback to SDK `SpanProcessor` interface. + ([#4024](https://github.com/open-telemetry/opentelemetry-specification/pull/4024)) + ### Metrics ### Logs @@ -41,8 +44,6 @@ release. ([#4064](https://github.com/open-telemetry/opentelemetry-specification/pull/4064)) - Add new in-development `Enabled` API to the `Tracer`. ([#4063](https://github.com/open-telemetry/opentelemetry-specification/pull/4063)) -- Add in-development `OnEnding` callback to SDK `SpanProcessor` interface. - ([#4024](https://github.com/open-telemetry/opentelemetry-specification/pull/4024)) ### Metrics From ffcb61441e5a2f1c892fca755196d4fa2e4eab32 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 26 Jun 2024 14:27:30 +0200 Subject: [PATCH 16/20] Marked SpanProcessor.OnEnding as optional --- specification/trace/sdk.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 70f0984ed48..79b0ac0752d 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -570,6 +570,12 @@ in the SDK: ### Interface definition +The `SpanProcessor` interface MUST declare the following methods: + * [OnStart](#onstart) + * [OnEnd](#onendspan) + +The `SpanProcessor` interface SHOULD declare the [OnEnding](#onending) method. + #### OnStart `OnStart` is called when a span is started. This method is called synchronously From 7cdc995453c1a0adbae508cb4b05d97fbc5606fc Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 26 Jun 2024 14:32:17 +0200 Subject: [PATCH 17/20] Add to compliance matrix --- spec-compliance-matrix.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spec-compliance-matrix.md b/spec-compliance-matrix.md index a7c8b203604..59461c0148b 100644 --- a/spec-compliance-matrix.md +++ b/spec-compliance-matrix.md @@ -55,6 +55,7 @@ formats is required. Implementing more than one format is optional. | events collection size limit | | + | + | + | + | + | + | + | + | - | - | + | | attribute collection size limit | | + | + | + | + | + | + | + | + | - | - | + | | links collection size limit | | + | + | + | + | + | + | + | + | - | - | + | +| [SpanProcessor.OnEnding](specification/trace/sdk.md#onending) callback supported | X | - | - | - | - | - | - | - | - | - | - | - | | [Span attributes](specification/trace/api.md#set-attributes) | Optional | Go | Java | JS | Python | Ruby | Erlang | PHP | Rust | C++ | .NET | Swift | | SetAttribute | | + | + | + | + | + | + | + | + | + | + | + | | Set order preserved | X | + | - | + | + | + | + | + | + | + | + | + | From a72ea8f15986a9cdd248797db6d51c5a89e49a78 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 26 Jun 2024 14:34:44 +0200 Subject: [PATCH 18/20] lint fix --- specification/trace/sdk.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 79b0ac0752d..16568c4af46 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -571,8 +571,9 @@ in the SDK: ### Interface definition The `SpanProcessor` interface MUST declare the following methods: - * [OnStart](#onstart) - * [OnEnd](#onendspan) + +* [OnStart](#onstart) +* [OnEnd](#onendspan) The `SpanProcessor` interface SHOULD declare the [OnEnding](#onending) method. From fe34f57bfd6764d249834f00b25b05098f937e77 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Thu, 27 Jun 2024 09:56:31 +0200 Subject: [PATCH 19/20] Added missing methods, adjust formatting --- specification/trace/sdk.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/specification/trace/sdk.md b/specification/trace/sdk.md index 16568c4af46..0d03c2b1ad1 100644 --- a/specification/trace/sdk.md +++ b/specification/trace/sdk.md @@ -574,8 +574,12 @@ The `SpanProcessor` interface MUST declare the following methods: * [OnStart](#onstart) * [OnEnd](#onendspan) +* [Shutdown](#shutdown-1) +* [ForceFlush](#forceflush-1) -The `SpanProcessor` interface SHOULD declare the [OnEnding](#onending) method. +The `SpanProcessor` interface SHOULD declare the following methods: + +* [OnEnding](#onending) method. #### OnStart From 49974363c61923965411d49c36c17ab8c800eb7c Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Mon, 15 Jul 2024 12:43:15 +0200 Subject: [PATCH 20/20] Review fixes Co-authored-by: Damien Mathieu <42@dmathieu.com> --- spec-compliance-matrix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec-compliance-matrix.md b/spec-compliance-matrix.md index 9894eba77a8..74f070ca46d 100644 --- a/spec-compliance-matrix.md +++ b/spec-compliance-matrix.md @@ -55,7 +55,7 @@ formats is required. Implementing more than one format is optional. | events collection size limit | | + | + | + | + | + | + | + | + | - | - | + | | attribute collection size limit | | + | + | + | + | + | + | + | + | - | - | + | | links collection size limit | | + | + | + | + | + | + | + | + | - | - | + | -| [SpanProcessor.OnEnding](specification/trace/sdk.md#onending) callback supported | X | - | - | - | - | - | - | - | - | - | - | - | +| [SpanProcessor.OnEnding](specification/trace/sdk.md#onending) | X | - | - | - | - | - | - | - | - | - | - | - | | [Span attributes](specification/trace/api.md#set-attributes) | Optional | Go | Java | JS | Python | Ruby | Erlang | PHP | Rust | C++ | .NET | Swift | | SetAttribute | | + | + | + | + | + | + | + | + | + | + | + | | Set order preserved | X | + | - | + | + | + | + | + | + | + | + | + |