DO-1529 fixed race condition in buildEventScheme generation

Descriptions in lifecycle group aren't generated because this group is initialized before resetDescriptions call.

GitOrigin-RevId: c276320f4eb2f7f5e578e081020aba2a76bdf022
This commit is contained in:
Liubov Afanaseva
2025-11-17 18:19:06 +00:00
committed by intellij-monorepo-bot
parent 8238bd08d7
commit d18c7b9992
6 changed files with 50 additions and 32 deletions
@@ -25,7 +25,8 @@ import com.intellij.testFramework.fixtures.BasePlatformTestCase
class EventSchemeBuilderTest : BasePlatformTestCase() {
override fun setUp() {
super.setUp()
RegisteredLogDescriptionsProcessor.reset(false)
RegisteredLogDescriptionsProcessor.configureDescriptionRegistration(false)
RegisteredLogDescriptionsProcessor.resetDescriptions()
}
fun `test generate string field validated by regexp`() {
@@ -129,7 +130,8 @@ class EventSchemeBuilderTest : BasePlatformTestCase() {
* when [RegisteredLogDescriptionsProcessor.isRegistered] is true.
*/
fun `test generate registered descriptions1`() {
RegisteredLogDescriptionsProcessor.reset(true)
RegisteredLogDescriptionsProcessor.configureDescriptionRegistration(true)
RegisteredLogDescriptionsProcessor.resetDescriptions()
val groupDescription = "Test group description5"
val eventDescription = "Description of test event"
val fieldDescription = "Number of elements in event"
@@ -152,7 +154,8 @@ class EventSchemeBuilderTest : BasePlatformTestCase() {
* registered group description.
*/
fun `test generate registered descriptions for the same groups with different descriptions`() {
RegisteredLogDescriptionsProcessor.reset(true)
RegisteredLogDescriptionsProcessor.configureDescriptionRegistration(true)
RegisteredLogDescriptionsProcessor.resetDescriptions()
val groupDescription = "Test group description"
val eventDescription = "Description of test event"
val fieldDescription = "Number of elements in event"
@@ -176,7 +179,8 @@ class EventSchemeBuilderTest : BasePlatformTestCase() {
* Validates the error message when attempting to overwrite an existing event description.
*/
fun `test generate registered descriptions for the same events with different descriptions`() {
RegisteredLogDescriptionsProcessor.reset(true)
RegisteredLogDescriptionsProcessor.configureDescriptionRegistration(true)
RegisteredLogDescriptionsProcessor.resetDescriptions()
val groupDescription = "Test group description"
val eventDescription = "Description of test event"
val fieldDescription = "Number of elements in event"
+5
View File
@@ -1,6 +1,9 @@
c:com.intellij.internal.statistic.eventLog.EventLogGroup
- b:<init>(java.lang.String,I,java.lang.String,java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:getGroupData():java.util.List
- f:registerEvent(java.lang.String,com.intellij.internal.statistic.eventLog.events.EventField,java.lang.String,I,java.lang.Object):com.intellij.internal.statistic.eventLog.events.EventId1
f:com.intellij.internal.statistic.eventLog.EventLogGroupKt
- sf:FUS_RECORDER:java.lang.String
com.intellij.internal.statistic.eventLog.StatisticsEventLogListener
- a:onLogEvent(com.jetbrains.fus.reporting.model.lion3.LogEvent,java.lang.String,java.util.Map):V
com.intellij.internal.statistic.eventLog.StatisticsEventLogger
@@ -12,3 +15,5 @@ com.intellij.internal.statistic.eventLog.StatisticsEventLogger
- a:logAsync(com.intellij.internal.statistic.eventLog.EventLogGroup,java.lang.String,kotlin.jvm.functions.Function0,Z):java.util.concurrent.CompletableFuture
- logAsync(com.intellij.internal.statistic.eventLog.EventLogGroup,java.lang.String,Z):java.util.concurrent.CompletableFuture
- a:rollOver():V
f:com.intellij.internal.statistic.eventLog.events.scheme.RegisteredLogDescriptionsProcessorKt
- sf:FUS_DESCRIPTION_REGISTRATION_ENABLED:java.lang.String
@@ -3,9 +3,12 @@ package com.intellij.internal.statistic.eventLog
import com.intellij.internal.statistic.IdeActivityDefinition
import com.intellij.internal.statistic.eventLog.events.*
import com.intellij.internal.statistic.eventLog.events.scheme.FUS_DESCRIPTION_REGISTRATION_ENABLED
import com.intellij.internal.statistic.eventLog.events.scheme.RegisteredLogDescriptionsProcessor
import org.jetbrains.annotations.NonNls
const val FUS_RECORDER: String = "FUS"
/**
* Represents a group of events used for feature usage statistics.
*
@@ -38,9 +41,8 @@ open class EventLogGroup {
* @param description A textual description of the event group.
* The description is not null and not an empty string.
* The description is registered at event group initialization using the RegisteredLogDescriptionsProcessor.
* There is no description in the memory if [RegisteredLogDescriptionsProcessor.isRegistered] is false.
* [RegisteredLogDescriptionsProcessor.isRegistered] is true just for
* [com.intellij.internal.statistic.eventLog.events.scheme.EventsSchemeBuilderAppStarter] and tests
* There is no description in the memory if the environment variable [FUS_DESCRIPTION_REGISTRATION_ENABLED] is false.
* Descriptions are stored in memory just for [com.intellij.internal.statistic.eventLog.events.scheme.EventsSchemeBuilderAppStarter] and unit tests.
* @param groupData EventFields in groupData are going to be appended to every event in the group. To provide the data, a supplier
* function is passed along with each EventField. See [com.intellij.internal.statistic.eventLog.events.EventId] for appending logic.
*
@@ -49,7 +51,7 @@ open class EventLogGroup {
constructor(
@NonNls @EventIdName id: String,
version: Int,
recorder: String,
recorder: String = FUS_RECORDER,
description: String,
groupData: List<Pair<EventField<*>, FeatureUsageData.() -> Unit>> = emptyList(),
) {
@@ -71,7 +73,7 @@ open class EventLogGroup {
constructor(
@NonNls @EventIdName id: String,
version: Int,
recorder: String = "FUS",
recorder: String = FUS_RECORDER,
) {
this.id = id
this.version = version
@@ -86,7 +88,7 @@ open class EventLogGroup {
constructor(
@NonNls @EventIdName id: String,
version: Int,
recorder: String,
recorder: String = FUS_RECORDER,
description: String?,
) : this(id, version, recorder) {
RegisteredLogDescriptionsProcessor.registerGroupDescription(id, description)
@@ -115,9 +117,8 @@ open class EventLogGroup {
* @param description The unique identifier for the event.
* The description is not null and not an empty string.
* The description is registered at event initialization using the [RegisteredLogDescriptionsProcessor].
* There is no description in the memory if [RegisteredLogDescriptionsProcessor.isRegistered] is false.
* [RegisteredLogDescriptionsProcessor.isRegistered] is true just
* for [com.intellij.internal.statistic.eventLog.events.scheme.EventsSchemeBuilderAppStarter] and tests
* There is no description in the memory if the environment variable [FUS_DESCRIPTION_REGISTRATION_ENABLED] is false.
* Descriptions are stored in memory just for [com.intellij.internal.statistic.eventLog.events.scheme.EventsSchemeBuilderAppStarter] and unit tests.
*
* For events with more than 3 fields use EventLogGroup.registerVarargEvent
*
@@ -6,6 +6,7 @@ import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.FeatureUsageData
import com.intellij.internal.statistic.eventLog.StatisticsEventLogProviderUtil
import com.intellij.internal.statistic.eventLog.StatisticsEventLogger
import com.intellij.internal.statistic.eventLog.events.scheme.FUS_DESCRIPTION_REGISTRATION_ENABLED
import com.intellij.internal.statistic.eventLog.events.scheme.RegisteredLogDescriptionsProcessor
import com.intellij.openapi.project.Project
import org.jetbrains.annotations.NonNls
@@ -24,8 +25,8 @@ import java.util.function.Consumer
* The description is not an empty string.
* The description is not null for new events.
* The description is registered at event initialization using the [RegisteredLogDescriptionsProcessor].
* There is no description in the memory if [RegisteredLogDescriptionsProcessor.isRegistered] is false.
* [RegisteredLogDescriptionsProcessor.isRegistered] is true just for [com.intellij.internal.statistic.eventLog.events.scheme.EventsSchemeBuilderAppStarter] and tests
* There is no description in the memory if the environment variable [FUS_DESCRIPTION_REGISTRATION_ENABLED] is false.
* Descriptions are stored in memory just for [com.intellij.internal.statistic.eventLog.events.scheme.EventsSchemeBuilderAppStarter] and unit tests.
*/
abstract class BaseEventId(groupId: String, val eventId: String, val recorder: String, description: String?) {
init {
@@ -52,7 +52,6 @@ internal class EventsSchemeBuilderAppStarter : ApplicationStarter {
testEventsScheme = arg.substringAfter(testEventSchemeParameter).toBoolean()
}
}
RegisteredLogDescriptionsProcessor.reset(true)
val groups: List<GroupDescriptor>
try {
groups = EventsSchemeBuilder.buildEventsScheme(recorderId, pluginId, getPluginsToSkipSchemeGeneration())
@@ -3,12 +3,14 @@ package com.intellij.internal.statistic.eventLog.events.scheme
import org.jetbrains.annotations.ApiStatus
const val FUS_DESCRIPTION_REGISTRATION_ENABLED: String = "FUS_DESCRIPTION_REGISTRATION_ENABLED"
/**
* Internal processor for managing to store descriptions of event log groups and their events.
* Provides functionality to store, retrieve descriptions for groups and their corresponding events.
*
* Descriptions are registered and stored in the memory if [isRegistered] is true.
* Please use it just for [com.intellij.internal.statistic.eventLog.events.scheme.EventsSchemeBuilderAppStarter] and tests.
* Descriptions are registered and stored in the memory if the environment variable [FUS_DESCRIPTION_REGISTRATION_ENABLED] is true.
* Please use it just for [EventsSchemeBuilderAppStarter] and tests.
*
* It ensures that event and group descriptions are immutable once registered.
* If an attempt is made to override an already registered description with a differing value,
@@ -23,25 +25,31 @@ import org.jetbrains.annotations.ApiStatus
object RegisteredLogDescriptionsProcessor {
private val groupDescriptionsMap = HashMap<String, String>()
private val eventDescriptionsMap = HashMap<String, HashMap<String, String>>()
private var isRegistered = false
private var descriptionRegistrationEnabled = System.getenv(FUS_DESCRIPTION_REGISTRATION_ENABLED)?.toBoolean() ?: false
/**
* Resets the state of the current instance by clearing all relevant data and updating registration status.
*
* @param isRegistered A boolean indicating the new registration status to set.
* Resets the descriptions of the current instance by clearing all relevant data.
*/
fun reset(isRegistered: Boolean) {
this.isRegistered = isRegistered
fun resetDescriptions() {
groupDescriptionsMap.clear()
eventDescriptionsMap.clear()
}
/**
* Register a description for a specified group if [isRegistered] is true, the description is not empty and not null.
* Enables/disables description registration.
*
* @param descriptionRegistrationEnabled A boolean indicating the new registration status to set.
*/
fun configureDescriptionRegistration(isEnabled: Boolean) {
this.descriptionRegistrationEnabled = isEnabled
}
/**
* Register a description for a specified group if [descriptionRegistrationEnabled] is true, the description is not empty and not null.
* Throws an exception if the description for the group already exists and differs from the new one.
*/
fun registerGroupDescription(groupId: String, description: String?) {
if (!isRegistered || description == null || description.isEmpty()) return
if (!descriptionRegistrationEnabled || description == null || description.isEmpty()) return
if (groupDescriptionsMap.containsKey(groupId) && groupDescriptionsMap[groupId] != description) {
throw IllegalStateException("Trying to override registered event log group description in group '$groupId'. " +
@@ -51,11 +59,11 @@ object RegisteredLogDescriptionsProcessor {
}
/**
* Register a description for an event within a specific group if [isRegistered] is true, the description is not empty and not null.
* Register a description for an event within a specific group if [descriptionRegistrationEnabled] is true, the description is not empty and not null.
* Throws an exception if the description for the event already exists and differs from the new one.
*/
fun registerEventDescription(groupId: String, eventId: String, description: String?) {
if (!isRegistered || description == null || description.isEmpty()) return
if (!descriptionRegistrationEnabled || description == null || description.isEmpty()) return
val groupMap = eventDescriptionsMap[groupId]
if (groupMap == null) {
@@ -75,18 +83,18 @@ object RegisteredLogDescriptionsProcessor {
}
/**
* Retrieves the description for a specified group or returns null if [isRegistered] is false or the group description is not registered.
* Retrieves the description for a specified group or returns null if [descriptionRegistrationEnabled] is false or the group description is not registered.
*/
fun calculateGroupDescription(groupId: String): String? {
if (!isRegistered) return null
if (!descriptionRegistrationEnabled) return null
return groupDescriptionsMap[groupId]
}
/**
* Retrieves the description for a specific event in the group or returns null if [isRegistered] is false or the events description is not registered.
* Retrieves the description for a specific event in the group or returns null if [descriptionRegistrationEnabled] is false or the events description is not registered.
*/
fun calculateEventDescription(groupId: String, eventId: String): String? {
if (!isRegistered) return null
if (!descriptionRegistrationEnabled) return null
val groupMap = eventDescriptionsMap[groupId]
return groupMap?.get(eventId)
}