Skip to content

Commit 12fe007

Browse files
dovchinnikovintellij-monorepo-bot
authored andcommitted
IDEA-312013 don't rely on shared flow hack in FUS logger
Hack in question: Kotlin/kotlinx.coroutines#2603 (comment) It was intended to ensure that the emitted value was collected, but it doesn't work with `collectLatest`. Thankfully, `collectLatest` is not needed here, because logs are dumped once in 12/24 hours. This change also ensures all coroutines work in correct scopes by getting rid of `application.coroutineScope` and `project.coroutineScope` usages. GitOrigin-RevId: 0f93cb3a59a722ee98936a93fcc1cb301e50a9f3
1 parent 88c63b8 commit 12fe007

File tree

5 files changed

+124
-130
lines changed

5 files changed

+124
-130
lines changed

platform/platform-impl/src/com/intellij/featureStatistics/StatisticsStateCollectorsTrigger.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ private class StatisticsStateCollectorsTrigger : AppLifecycleListener {
2828
// only proceed if IDE opens with a welcome screen and stays idle on it for some time
2929
val welcomeFrame = WelcomeFrame.getInstance()
3030
if (welcomeFrame != null && welcomeFrame == ref.get()) {
31-
FUStateUsagesLogger.getInstance().logApplicationStatesOnStartup()
31+
FUStateUsagesLogger.getInstance().scheduleLogApplicationStatesOnStartup()
3232
}
3333
}
3434
}

platform/statistics/devkit/src/com/intellij/internal/statistic/devkit/actions/FusStatesRecorder.kt

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
1+
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
22
package com.intellij.internal.statistic.devkit.actions
33

44
import com.intellij.internal.statistic.eventLog.EventLogListenersManager
55
import com.intellij.internal.statistic.eventLog.StatisticsEventLogListener
66
import com.intellij.internal.statistic.eventLog.StatisticsEventLogProviderUtil.getEventLogProvider
77
import com.intellij.internal.statistic.eventLog.StatisticsFileEventLogger
8-
import com.intellij.internal.statistic.eventLog.fus.FeatureUsageStateEventTracker
9-
import com.intellij.internal.statistic.service.fus.collectors.FUStateUsagesLogger
8+
import com.intellij.internal.statistic.service.fus.collectors.ProjectFUStateUsagesLogger
109
import com.intellij.openapi.components.service
1110
import com.intellij.openapi.diagnostic.logger
1211
import com.intellij.openapi.project.Project
1312
import com.jetbrains.fus.reporting.model.lion3.LogEvent
14-
import kotlinx.coroutines.async
15-
import kotlinx.coroutines.coroutineScope
1613
import kotlinx.coroutines.future.asCompletableFuture
17-
import kotlinx.coroutines.launch
1814
import java.util.concurrent.CompletableFuture
1915
import java.util.concurrent.ConcurrentLinkedQueue
2016
import java.util.concurrent.TimeUnit
@@ -37,21 +33,9 @@ internal object FusStatesRecorder {
3733
}
3834
service<EventLogListenersManager>().subscribe(subscriber, recorderId)
3935
try {
40-
project.coroutineScope.async {
41-
coroutineScope {
42-
launch {
43-
val stateLogger = FUStateUsagesLogger.getInstance()
44-
stateLogger.logApplicationStates()
45-
stateLogger.logProjectStates(project)
46-
}
47-
48-
for (extension in FeatureUsageStateEventTracker.EP_NAME.extensionList) {
49-
launch {
50-
extension.reportNow()
51-
}
52-
}
53-
}
54-
}.asCompletableFuture()
36+
project.service<ProjectFUStateUsagesLogger>()
37+
.scheduleLogApplicationAndProjectState()
38+
.asCompletableFuture()
5539
.thenCompose {
5640
val logger = getEventLogProvider(recorderId).logger
5741
if (logger is StatisticsFileEventLogger) {

platform/statistics/src/com/intellij/internal/statistic/service/fus/collectors/FUStateUsagesLogger.kt

Lines changed: 105 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,31 @@ import com.intellij.internal.statistic.eventLog.FeatureUsageData
1010
import com.intellij.internal.statistic.eventLog.StatisticsEventLogProviderUtil.getEventLogProvider
1111
import com.intellij.internal.statistic.eventLog.StatisticsEventLogger
1212
import com.intellij.internal.statistic.eventLog.fus.FeatureUsageLogger.logState
13+
import com.intellij.internal.statistic.eventLog.fus.FeatureUsageStateEventTracker
1314
import com.intellij.internal.statistic.service.fus.collectors.FUStateUsagesLogger.Companion.LOG
15+
import com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler
16+
import com.intellij.internal.statistic.utils.StatisticsUploadAssistant
1417
import com.intellij.internal.statistic.utils.getPluginInfo
1518
import com.intellij.openapi.application.ApplicationManager
1619
import com.intellij.openapi.components.Service
1720
import com.intellij.openapi.components.service
1821
import com.intellij.openapi.diagnostic.Logger
1922
import com.intellij.openapi.progress.blockingContext
2023
import com.intellij.openapi.project.Project
24+
import com.intellij.openapi.project.waitForSmartMode
2125
import kotlinx.coroutines.*
22-
import kotlinx.coroutines.flow.MutableSharedFlow
23-
import kotlinx.coroutines.flow.collectLatest
24-
import kotlinx.coroutines.flow.filterNotNull
2526
import kotlinx.coroutines.future.asDeferred
2627
import org.jetbrains.annotations.ApiStatus.Internal
28+
import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
2729
import org.jetbrains.concurrency.asDeferred
30+
import kotlin.time.Duration.Companion.hours
31+
import kotlin.time.Duration.Companion.minutes
32+
33+
private val LOG_APPLICATION_STATES_INITIAL_DELAY = 10.minutes
34+
private val LOG_APPLICATION_STATES_DELAY = 24.hours
35+
private val LOG_PROJECTS_STATES_INITIAL_DELAY = 5.minutes
36+
private val LOG_PROJECTS_STATES_DELAY = 12.hours
37+
private const val REDUCE_DELAY_FLAG_KEY = "fus.internal.reduce.initial.delay"
2838

2939
/**
3040
* Called by a scheduler once a day and records IDE/project state. <br></br>
@@ -37,15 +47,14 @@ import org.jetbrains.concurrency.asDeferred
3747
*/
3848
@Service(Service.Level.APP)
3949
@Internal
40-
class FUStateUsagesLogger private constructor(cs: CoroutineScope) : UsagesCollectorConsumer {
41-
// https://github.com/Kotlin/kotlinx.coroutines/issues/2603#issuecomment-808859170
42-
private val logAppStateRequests = MutableSharedFlow<Boolean?>()
50+
class FUStateUsagesLogger private constructor(private val cs: CoroutineScope) : UsagesCollectorConsumer {
4351

4452
init {
4553
cs.launch {
46-
logAppStateRequests
47-
.filterNotNull()
48-
.collectLatest(::logApplicationStates)
54+
if (!StatisticsUploadAssistant.isSendAllowed()) {
55+
return@launch
56+
}
57+
logApplicationStateRegularly()
4958
}
5059
}
5160

@@ -141,18 +150,22 @@ class FUStateUsagesLogger private constructor(cs: CoroutineScope) : UsagesCollec
141150
}
142151
}
143152

144-
suspend fun logProjectStates(project: Project) {
145-
project.service<ProjectFUStateUsagesLogger>().logProjectStates()
153+
private suspend fun logApplicationStateRegularly() {
154+
StatisticsStateCollectorsScheduler.allowExecution.set(true)
155+
delay(LOG_APPLICATION_STATES_INITIAL_DELAY)
156+
StatisticsStateCollectorsScheduler.allowExecution.set(false)
157+
while (true) {
158+
logApplicationStates(onStartup = false)
159+
delay(LOG_APPLICATION_STATES_DELAY)
160+
}
146161
}
147162

148-
suspend fun logApplicationStates() {
149-
logAppStateRequests.emit(false)
150-
logAppStateRequests.emit(null)
163+
fun scheduleLogApplicationStatesOnStartup(): Job = cs.launch {
164+
logApplicationStates(onStartup = true)
151165
}
152166

153-
suspend fun logApplicationStatesOnStartup() {
154-
logAppStateRequests.emit(true)
155-
logAppStateRequests.emit(null)
167+
fun scheduleLogApplicationState(): Job = cs.launch {
168+
logApplicationStates(onStartup = false)
156169
}
157170

158171
private suspend fun logApplicationStates(onStartup: Boolean) {
@@ -179,47 +192,91 @@ class FUStateUsagesLogger private constructor(cs: CoroutineScope) : UsagesCollec
179192
}
180193
}
181194
}
195+
196+
@ScheduledForRemoval
197+
@Deprecated(
198+
message = "Use ProjectFUStateUsagesLogger.scheduleLogProjectState",
199+
ReplaceWith(
200+
"project.service<ProjectFUStateUsagesLogger>().scheduleLogProjectState().join()",
201+
"com.intellij.openapi.components.service"
202+
),
203+
)
204+
suspend fun logProjectStates(project: Project) {
205+
project.service<ProjectFUStateUsagesLogger>().scheduleLogProjectState().join()
206+
}
207+
208+
@ScheduledForRemoval
209+
@Deprecated(
210+
message = "Use FUStateUsagesLogger.scheduleLogProjectState",
211+
replaceWith = ReplaceWith(
212+
"scheduleLogApplicationState().join()"
213+
),
214+
)
215+
suspend fun logApplicationStates() {
216+
scheduleLogApplicationState().join()
217+
}
182218
}
183219

220+
@Internal
184221
@Service(Service.Level.PROJECT)
185-
private class ProjectFUStateUsagesLogger(
222+
class ProjectFUStateUsagesLogger(
186223
private val project: Project,
187-
cs: CoroutineScope,
224+
private val cs: CoroutineScope,
188225
) : UsagesCollectorConsumer {
189-
// https://github.com/Kotlin/kotlinx.coroutines/issues/2603#issuecomment-808859170
190-
private val logProjectStateRequests = MutableSharedFlow<Unit?>()
191226

192227
init {
193228
cs.launch {
194-
logProjectStateRequests
195-
.filterNotNull()
196-
.collectLatest {
197-
coroutineScope {
198-
val recorderLoggers = HashMap<String, StatisticsEventLogger>()
199-
for (usagesCollector in ProjectUsagesCollector.getExtensions(this@ProjectFUStateUsagesLogger)) {
200-
if (!getPluginInfo(usagesCollector.javaClass).isDevelopedByJetBrains()) {
201-
@Suppress("removal", "DEPRECATION")
202-
LOG.warn("Skip '${usagesCollector.groupId}' because its registered in a third-party plugin")
203-
continue
204-
}
205-
206-
launch {
207-
val metrics = blockingContext { usagesCollector.getMetrics(project, null) }
208-
FUStateUsagesLogger.logMetricsOrError(
209-
project = project,
210-
recorderLoggers = recorderLoggers,
211-
usagesCollector = usagesCollector,
212-
metrics = metrics.asDeferred().await() ?: emptySet(),
213-
)
214-
}
215-
}
216-
}
217-
}
229+
project.waitForSmartMode()
230+
logProjectStateRegularly()
231+
}
232+
}
233+
234+
private suspend fun logProjectStateRegularly() {
235+
val reduceInitialDelay = System.getProperty(REDUCE_DELAY_FLAG_KEY).toBoolean()
236+
if (!reduceInitialDelay) {
237+
delay(LOG_PROJECTS_STATES_INITIAL_DELAY)
238+
}
239+
while (true) {
240+
logProjectState()
241+
delay(LOG_PROJECTS_STATES_DELAY)
242+
}
243+
}
244+
245+
fun scheduleLogProjectState(): Job = cs.launch {
246+
logProjectState()
247+
}
248+
249+
private suspend fun logProjectState(): Unit = coroutineScope {
250+
val recorderLoggers = HashMap<String, StatisticsEventLogger>()
251+
for (usagesCollector in ProjectUsagesCollector.getExtensions(this@ProjectFUStateUsagesLogger)) {
252+
if (!getPluginInfo(usagesCollector.javaClass).isDevelopedByJetBrains()) {
253+
@Suppress("removal", "DEPRECATION")
254+
LOG.warn("Skip '${usagesCollector.groupId}' because its registered in a third-party plugin")
255+
continue
256+
}
257+
258+
launch {
259+
val metrics = blockingContext { usagesCollector.getMetrics(project, null) }
260+
FUStateUsagesLogger.logMetricsOrError(
261+
project = project,
262+
recorderLoggers = recorderLoggers,
263+
usagesCollector = usagesCollector,
264+
metrics = metrics.asDeferred().await() ?: emptySet(),
265+
)
266+
}
218267
}
219268
}
220269

221-
suspend fun logProjectStates() {
222-
logProjectStateRequests.emit(Unit)
223-
logProjectStateRequests.emit(null)
270+
fun scheduleLogApplicationAndProjectState(): Deferred<Unit> = cs.async {
271+
launch {
272+
FUStateUsagesLogger.getInstance().scheduleLogApplicationState().join()
273+
logProjectState()
274+
}
275+
276+
for (extension in FeatureUsageStateEventTracker.EP_NAME.extensions) {
277+
launch {
278+
extension.reportNow()
279+
}
280+
}
224281
}
225282
}

platform/statistics/src/com/intellij/internal/statistic/updater/StatisticsStateCollectorsScheduler.kt

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
1+
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
22
package com.intellij.internal.statistic.updater
33

44
import com.intellij.ide.ApplicationInitializedListener
55
import com.intellij.ide.lightEdit.LightEdit
66
import com.intellij.internal.statistic.service.fus.collectors.FUStateUsagesLogger
7-
import com.intellij.internal.statistic.utils.StatisticsUploadAssistant
7+
import com.intellij.internal.statistic.service.fus.collectors.ProjectFUStateUsagesLogger
8+
import com.intellij.openapi.application.ApplicationManager
9+
import com.intellij.openapi.components.service
810
import com.intellij.openapi.project.DumbService
911
import com.intellij.openapi.project.Project
1012
import com.intellij.openapi.project.ProjectManager
@@ -13,38 +15,18 @@ import kotlinx.coroutines.CoroutineScope
1315
import kotlinx.coroutines.delay
1416
import kotlinx.coroutines.launch
1517
import java.util.concurrent.atomic.AtomicBoolean
16-
import kotlin.time.Duration.Companion.hours
1718
import kotlin.time.Duration.Companion.minutes
1819

1920
internal class StatisticsStateCollectorsScheduler : ApplicationInitializedListener {
2021
companion object {
21-
private val LOG_APPLICATION_STATES_INITIAL_DELAY = 10.minutes
22-
private val LOG_APPLICATION_STATES_DELAY = 24.hours
2322
private val LOG_APPLICATION_STATE_SMART_MODE_DELAY = 1.minutes
24-
private val LOG_PROJECTS_STATES_INITIAL_DELAY = 5.minutes
25-
private val LOG_PROJECTS_STATES_DELAY = 12.hours
26-
private const val REDUCE_DELAY_FLAG_KEY = "fus.internal.reduce.initial.delay"
2723

28-
private val allowExecution = AtomicBoolean(true)
24+
// avoid overlapping logging from periodic scheduler and OneTimeLogger (long indexing case)
25+
internal val allowExecution = AtomicBoolean(true) // TODO get rid of this
2926
}
3027

3128
override suspend fun execute(asyncScope: CoroutineScope) {
32-
asyncScope.launch {
33-
if (!StatisticsUploadAssistant.isSendAllowed()) {
34-
return@launch
35-
}
36-
37-
// avoid overlapping logging from periodic scheduler and OneTimeLogger (long indexing case)
38-
allowExecution.set(true)
39-
40-
delay(LOG_APPLICATION_STATES_INITIAL_DELAY)
41-
allowExecution.set(false)
42-
FUStateUsagesLogger.getInstance().logApplicationStates()
43-
while (true) {
44-
delay(LOG_APPLICATION_STATES_DELAY)
45-
FUStateUsagesLogger.getInstance().logApplicationStates()
46-
}
47-
}
29+
ApplicationManager.getApplication().service<FUStateUsagesLogger>() // init service
4830
}
4931

5032
internal class MyStartupActivity : ProjectPostStartupActivity {
@@ -54,21 +36,8 @@ internal class StatisticsStateCollectorsScheduler : ApplicationInitializedListen
5436
return
5537
}
5638

57-
// wait until initial indexation will be finished
58-
DumbService.getInstance(project).runWhenSmart {
59-
project.coroutineScope.launch {
60-
val reduceInitialDelay = System.getProperty(REDUCE_DELAY_FLAG_KEY).toBoolean()
61-
if (!reduceInitialDelay) {
62-
delay(LOG_PROJECTS_STATES_INITIAL_DELAY)
63-
}
64-
FUStateUsagesLogger.getInstance().logProjectStates(project)
39+
project.service<ProjectFUStateUsagesLogger>() // init service
6540

66-
while (true) {
67-
delay(LOG_PROJECTS_STATES_DELAY)
68-
FUStateUsagesLogger.getInstance().logProjectStates(project)
69-
}
70-
}
71-
}
7241
if (allowExecution.get()) {
7342
DumbService.getInstance(project).runWhenSmart {
7443
// wait until all projects will exit dumb mode
@@ -85,7 +54,7 @@ internal class StatisticsStateCollectorsScheduler : ApplicationInitializedListen
8554
if (allowExecution.getAndSet(false)) {
8655
project.coroutineScope.launch {
8756
delay(LOG_APPLICATION_STATE_SMART_MODE_DELAY)
88-
FUStateUsagesLogger.getInstance().logApplicationStatesOnStartup()
57+
FUStateUsagesLogger.getInstance().scheduleLogApplicationStatesOnStartup()
8958
}
9059
}
9160
}

plugins/performanceTesting/src/com/jetbrains/performancePlugin/commands/RecordStateCollectorsCommand.kt

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,19 @@
22
package com.jetbrains.performancePlugin.commands
33

44
import com.intellij.internal.statistic.eventLog.fus.FeatureUsageLogger
5-
import com.intellij.internal.statistic.eventLog.fus.FeatureUsageStateEventTracker
6-
import com.intellij.internal.statistic.service.fus.collectors.FUStateUsagesLogger
5+
import com.intellij.internal.statistic.service.fus.collectors.ProjectFUStateUsagesLogger
6+
import com.intellij.openapi.components.service
77
import com.intellij.openapi.ui.playback.PlaybackContext
88
import com.intellij.openapi.ui.playback.commands.AbstractCommand
9-
import kotlinx.coroutines.async
10-
import kotlinx.coroutines.coroutineScope
119
import kotlinx.coroutines.future.asCompletableFuture
12-
import kotlinx.coroutines.launch
1310
import org.jetbrains.concurrency.AsyncPromise
1411
import org.jetbrains.concurrency.Promise
1512
import java.util.concurrent.CompletableFuture
1613

1714
internal class RecordStateCollectorsCommand(text: String, line: Int) : AbstractCommand(text, line) {
1815
override fun _execute(context: PlaybackContext): Promise<Any?> {
19-
val stateLogger = FUStateUsagesLogger.getInstance()
20-
return context.project.coroutineScope.async {
21-
coroutineScope {
22-
launch {
23-
stateLogger.logApplicationStates()
24-
stateLogger.logProjectStates(context.project)
25-
}
26-
27-
for (extension in FeatureUsageStateEventTracker.EP_NAME.extensions) {
28-
launch {
29-
extension.reportNow()
30-
}
31-
}
32-
}
33-
}
16+
return context.project.service<ProjectFUStateUsagesLogger>()
17+
.scheduleLogApplicationAndProjectState()
3418
.asCompletableFuture()
3519
.thenCompose { FeatureUsageLogger.flush() }
3620
.toPromise()

0 commit comments

Comments
 (0)