Hi <@UHAJKUSTU>, Is there a reason `TestObservable...
# reaktive
o
Hi @Arkadii Ivanov, Is there a reason
TestObservable
freezes? I'm been forced to use atomics instead of regular `var`s (i.e. to capture values, etc.) otherwise I get
InvalidMutabilityException
, if I remove it everything works fine. At least could it be optional? like
TestObservableObserver(autoFreeze)
a
This is the opt-out behavior. You can write
myObservable.test(autoFreeze = false).
o
Hi, sorry, now I realize I made a mistake, I was meaning
TestScheduler
a
TestScheduler
freezes by design. Because in real app schedulers freeze as well.
So you could test your code for compatibility with K/N memory model.
We can consider making it optional, but please provide a real use case.
o
Yes, that makes sense, the issues I have is not in the code, but in the tests
a
Would mind to post a code snippet?
o
Here, it freezes
capturedAdd
, so I need to use an
AtomicXXX
instead, and that slower, since I use property checks
a
Could you please expand the
LogCacheBaseMock
? It's not clear what's inside
o
For sure, it's just a base test class
This is my poor-man solution for mocking
a
Where is the
TestScheduler
used?
o
It is used to sync the cache ops
a
So if I understand correctly this particular failure is valid. You are creating the anonymous class from
LogCacheBaseMock
so it captures the outer state (the variable). And then it gets frozen so everything captured is frozen as well. If you will do this in production you will have troubles.
TestScheduler
is designed to behave as real as possible.
I would recommend to rename
LogCacheBaseMock
to
TestLogCache
and implement "fake" logging inside. E.g. accumulate events into
AtomicReference<List<LogData>>
. You can then expose a handy property like
val data: List<LogData> get() = ref.value
o
Yes, just using a
AtomicReference
with my code works, but then it is much slower, since every test case is run 500 times with random data
a
Well this is how your production code will be working, isn't it?
o
Prod code is fine, even using a plain
List
since all ops happens in the same thread, the problem is with the test
a
Wait, how is it fine if it gets frozen?
Or do you mean it freezes only in tests?
o
yes, that's right
a
But you presented that you are using scheduler
Is it only in tests?
o
Nope
In prod
a
So the
logCache
list will be frozen
o
Well 🤷‍♂️ let me see if I can create something reproducible
a
That will be very helpful, thanks
o
Ok, now I know why is working in prod, I'm using the
MainScheduler
directly (
MainScheduler()
instead of using
mainScheduler
) I had to copied locally because it's internal (I double check and is the same, no changes on my side)
Given:
Copy code
class LongStore {
    private var _value: Long = 0

    fun apply(value: Long) {
        _value = value
    }

    fun get(): Long = _value
}

class UseCase(store: LongStore, scheduler: Scheduler) {

    private val disposable = CompositeDisposable()

    init {
        disposable += observableInterval(300, scheduler)
            .subscribe { store.apply(it) }
    }
}
This doesn't work
Copy code
val store = LongStore()
        val useCase = UseCase(store, mainScheduler)
But this does
Copy code
val store = LongStore()
        val useCase = UseCase(store, MainScheduler())
And this is the behaviour I'd expect, since I'm always on the main thread, so no need to freeze
Also it's maybe the original intent of the MainScheduler
a
This behaviour is not guaranteed. What you found is just my failed attempt to avoid freezing by the main scheduler. The
Operation
is added to the list, which is stored in the
Executor
.
Executor
itself is stored in the
MainScheduler
. The
MainScheduler
(as well as all other default schedulers) is frozen, so all
Executors
are frozen as well, and so all their data is frozen.
Operation
gets frozen anyway. Your
log
method schedules a callback to the main thread. So I assume that the
log
method may be called from any thread. If this statement is true, then you callback will be frozen anyway (regardless of the scheduler being used, and if it is frozen or not). If this statement is false (the
log
method is called only from the main thread), then you don't need scheduler at all.
Also please use the provided property
mainScheduler
, no need to create a new scheduler every time.
In general - you can't submit a callback from background thread to the main thread without freezing. Regardless of a framework being used: Reaktive, coroutines or anything else. This is KN's limitation.
o
Gotcha
It's really disappointing, especially since Atomics tend to be slow
Thanks a lot for the clarification
a
Welcome to the Kotlin/Native world 🙂 You can try using Stately
Isolate
thing (https://github.com/touchlab/Stately#stately-isolate) . It may give better performance. It creates a dedicated thread for the mutable state and communicates via events.
o
Cool, thanks