RxJava keep Emiting item after dispose, How to dispose it properly? - kotlin

I tried to dispose RxJava disposable in ViewModel. I'm pretry sure it is a good practice to clear disposables in onCleard method of ViewModel, so it doesn't leakage ViewModel itself. But, when there's an UnknownHostException, right before dispose action happened, it's make ViewModel leakage. I opened leak canary and I it says that onError related stuff has made this leaks.
I figured out that UnknownHostException is acctually catch under Global error handler of RxJava, so why this exception keep referencing onError callback and makes leakage, even after I dispose the disposable.
UseCase.kt
abstract class UseCase {
protected var lastDisposable: Disposable? = null
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
fun disposeLast() {
lastDisposable?.let {
if (!it.isDisposed) {
it.dispose()
}
}
}
fun dispose() {
compositeDisposable.clear()
}
fun Disposable.addDisposable() {
compositeDisposable.add(this)
}
class None()
}
FlowableUseCase.kt
abstract class FlowableUseCase<in Params, Model> #Inject constructor(private val flowableRxTransformer: FlowableRxTransformer<Model>) :
UseCase() {
internal abstract fun buildUseCaseFlowable(params: Params): Flowable<Model>
operator fun invoke(
params: Params,
onLoading: () -> Unit = {},
onError: ((error: Throwable) -> Unit) = {},
onSuccess: ((entity: Model) -> Unit) = {},
onFinished: () -> Unit = {}
) {
onLoading()
disposeLast()
lastDisposable = buildUseCaseFlowable(params)
.compose(flowableRxTransformer)
.doAfterTerminate(onFinished)
.subscribe(onSuccess, onError) // leaks happened here
lastDisposable?.addDisposable()
}
}
If I tried to remove onError Callback there is no error anymore. So I'm pretty sure that its caused memory leaks, but this action is pottentially make OnErrorNotImplementedException so I don't want to do that.
Any kind of advice is acceptable. Thanks in advance.
Leak Canary Head Dump
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ Leaking: NO (App↓ is not leaking and a class is never leaking)
│ ↓ static FontsContract.sContext
├─ com.potatocandie.cleanprayertime.App instance
│ Leaking: NO (Application is a singleton)
│ mBase instance of android.app.ContextImpl, not wrapping known Android
│ context
│ ↓ App.componentManager
│ ~~~~~~~~~~~~~~~~
├─ dagger.hilt.android.internal.managers.ApplicationComponentManager instance
│ Leaking: UNKNOWN
│ Retaining 40 bytes in 3 objects
│ ↓ ApplicationComponentManager.component
│ ~~~~~~~~~
├─ com.potatocandie.cleanprayertime.DaggerApp_HiltComponents_SingletonC instance
│ Leaking: UNKNOWN
│ Retaining 7731 bytes in 279 objects
│ ↓ DaggerApp_HiltComponents_SingletonC.okHttpClientBuilder
│ ~~~~~~~~~~~~~~~~~~~
├─ okhttp3.OkHttpClient$Builder instance
│ Leaking: UNKNOWN
│ Retaining 203 bytes in 4 objects
│ ↓ OkHttpClient$Builder.dispatcher
│ ~~~~~~~~~~
├─ okhttp3.Dispatcher instance
│ Leaking: UNKNOWN
│ Retaining 288 bytes in 7 objects
│ ↓ Dispatcher.runningAsyncCalls
│ ~~~~~~~~~~~~~~~~~
├─ java.util.ArrayDeque instance
│ Leaking: UNKNOWN
│ Retaining 84 bytes in 2 objects
│ ↓ ArrayDeque.elements
│ ~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ Retaining 64 bytes in 1 objects
│ ↓ Object[].[0]
│ ~~~
├─ okhttp3.internal.connection.RealCall$AsyncCall instance
│ Leaking: UNKNOWN
│ Retaining 3815 bytes in 123 objects
│ ↓ RealCall$AsyncCall.responseCallback
│ ~~~~~~~~~~~~~~~~
├─ retrofit2.OkHttpCall$1 instance
│ Leaking: UNKNOWN
│ Retaining 3795 bytes in 122 objects
│ Anonymous class implementing okhttp3.Callback
│ ↓ OkHttpCall$1.val$callback
│ ~~~~~~~~~~~~
├─ retrofit2.adapter.rxjava3.CallEnqueueObservable$CallCallback instance
│ Leaking: UNKNOWN
│ Retaining 3705 bytes in 119 objects
│ ↓ CallEnqueueObservable$CallCallback.observer
│ ~~~~~~~~
├─ retrofit2.adapter.rxjava3.BodyObservable$BodyObserver instance
│ Leaking: UNKNOWN
│ Retaining 3687 bytes in 118 objects
│ ↓ BodyObservable$BodyObserver.observer
│ ~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.
│ FlowableFromObservable$SubscriberObserver instance
│ Leaking: UNKNOWN
│ Retaining 3674 bytes in 117 objects
│ ↓ FlowableFromObservable$SubscriberObserver.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.
│ FlowableOnBackpressureLatest$BackpressureLatestSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 3658 bytes in 116 objects
│ ↓ FlowableOnBackpressureLatest$BackpressureLatestSubscriber.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.FlowableMap$MapSubscriber
│ instance
│ Leaking: UNKNOWN
│ Retaining 3596 bytes in 113 objects
│ ↓ FlowableMap$MapSubscriber.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.
│ FlowableDoOnEach$DoOnEachSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 3564 bytes in 112 objects
│ ↓ FlowableDoOnEach$DoOnEachSubscriber.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.
│ FlowableRetryWhen$RetryWhenSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 3400 bytes in 105 objects
│ ↓ FlowableRetryWhen$RetryWhenSubscriber.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.subscribers.SerializedSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 2860 bytes in 79 objects
│ ↓ SerializedSubscriber.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.
│ FlowableDoOnEach$DoOnEachSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 2837 bytes in 78 objects
│ ↓ FlowableDoOnEach$DoOnEachSubscriber.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.
│ FlowableFlatMap$InnerSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 2781 bytes in 76 objects
│ ↓ FlowableFlatMap$InnerSubscriber.parent
│ ~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.
│ FlowableFlatMap$MergeSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 2732 bytes in 75 objects
│ ↓ FlowableFlatMap$MergeSubscriber.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.FlowableMap$MapSubscriber
│ instance
│ Leaking: UNKNOWN
│ Retaining 1837 bytes in 58 objects
│ ↓ FlowableMap$MapSubscriber.downstream
│ ~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.operators.flowable.
│ FlowableDoOnEach$DoOnEachSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 1793 bytes in 56 objects
│ ↓ FlowableDoOnEach$DoOnEachSubscriber.onNext
│ ~~~~~~
├─ com.potatocandie.domain.base.FlowableUseCase$invoke$5 instance
│ Leaking: UNKNOWN
│ Retaining 12 bytes in 1 objects
│ Anonymous class implementing io.reactivex.rxjava3.functions.Consumer
│ ↓ FlowableUseCase$invoke$5.this$0
│ ~~~~~~
├─ com.potatocandie.domain.usecases.GetCurrentMonthsPrayerTimesUseCase instance
│ Leaking: UNKNOWN
│ Retaining 45 bytes in 3 objects
│ ↓ GetCurrentMonthsPrayerTimesUseCase.lastDisposable
│ ~~~~~~~~~~~~~~
├─ io.reactivex.rxjava3.internal.subscribers.LambdaSubscriber instance
│ Leaking: UNKNOWN
│ Retaining 800 bytes in 34 objects
│ ↓ LambdaSubscriber.onError
│ ~~~~~~~
├─ com.potatocandie.domain.base.
│ FlowableUseCase$sam$io_reactivex_rxjava3_functions_Consumer$0 instance
│ Leaking: UNKNOWN
│ Retaining 760 bytes in 32 objects
│ Anonymous class implementing io.reactivex.rxjava3.functions.Consumer
│ ↓ FlowableUseCase$sam$io_reactivex_rxjava3_functions_Consumer$0.function
│ ~~~~~~~~
├─ com.potatocandie.cleanprayertime.features.today.
│ PrayerTimesViewModel$getCurrentMonthsPrayerTimes$2 instance
│ Leaking: UNKNOWN
│ Retaining 748 bytes in 31 objects
│ Anonymous subclass of kotlin.jvm.internal.Lambda
│ ↓ PrayerTimesViewModel$getCurrentMonthsPrayerTimes$2.this$0
│ ~~~~~~
╰→ com.potatocandie.cleanprayertime.features.today.PrayerTimesViewModel instance
​ Leaking: YES (ObjectWatcher was watching this because com.potatocandie.
​ cleanprayertime.features.today.PrayerTimesViewModel received
​ ViewModel#onCleared() callback)
​ Retaining 732 bytes in 30 objects
​ key = 1a238931-2884-4446-a5c2-66d46390134f
​ watchDurationMillis = 7824
​ retainedDurationMillis = 2822

Related

Writing long rows using polars DataFrame throws runtime error

I have the following async block of code that runs as part of a larger program, and it runs successfully when the dataframe has a row with length 10, or 30, but when i put it to a larger number like 300, it tries to write the dataframe as parquet and throws a runtime error for each async thread:
Here is an example of the df it tries to write but fails.
df: Ok(shape: (300, 4)
┌───────────────┬─────────┬────────────────────────────────┬───────────────────────────────────────┐
│ timestamp ┆ ticker ┆ bids ┆ asks │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ list[list[f64]] ┆ list[list[f64]] │
╞═══════════════╪═════════╪════════════════════════════════╪═══════════════════════════════════════╡
│ 1674962575119 ┆ ETHUSDT ┆ [[1589.51, 4.731], [1589.31, ┆ [[1590.93, 39.234], [1592.1, 51.... │
│ ┆ ┆ 93.... ┆ │
│ 1674962575220 ┆ ETHUSDT ┆ [[1589.51, 22.094], [1589.31, ┆ [[1590.93, 39.234], [1592.1, 51.... │
│ ┆ ┆ 24... ┆ │
│ 1674962575319 ┆ ETHUSDT ┆ [[1589.51, 12.324], [1589.31, ┆ [[1590.93, 39.309], [1592.1, 52.... │
│ ┆ ┆ 24... ┆ │
│ 1674962575421 ┆ ETHUSDT ┆ [[1589.51, 0.0], [1589.31, ┆ [[1590.93, 26.735], [1592.1, 52.... │
│ ┆ ┆ 24.26... ┆ │
│ ... ┆ ... ┆ ... ┆ ... │
│ 1674962604998 ┆ ETHUSDT ┆ [[1440.0, 5138.446], [1558.38, ┆ [[1617.28, 40.969], [1593.72, 3.... │
│ ┆ ┆ 0... ┆ │
│ 1674962605101 ┆ ETHUSDT ┆ [[1440.0, 5138.446], [1558.38, ┆ [[1617.28, 40.969], [1593.72, 3.... │
│ ┆ ┆ 0... ┆ │
│ 1674962605201 ┆ ETHUSDT ┆ [[1440.0, 5138.446], [1558.38, ┆ [[1617.28, 40.969], [1593.72, 3.... │
│ ┆ ┆ 0... ┆ │
│ 1674962605301 ┆ ETHUSDT ┆ [[1440.0, 5138.446], [1558.38, ┆ [[1617.28, 40.969], [1593.72, 3.... │
│ ┆ ┆ 0... ┆ │
└───────────────┴─────────┴────────────────────────────────┴───────────────────────────────────────┘)
Here is the error.
thread '<unnamed>' panicked at 'range end index 131373 out of range for slice of length 301', C:\Users\username\.cargo\git\checkouts\arrow2-945af624853845da\baa2618\src\io\parquet\write\mod.rs:171:37
thread '<unnamed>' panicked at 'range end index 131373 out of range for slice of length 301', C:\Users\username\.cargo\git\checkouts\arrow2-945af624853845da\baa2618\src\io\parquet\write\mod.rs:171:37
Here is the code.
async {
if main_vec.length() >= ROWS {
let df = main_vec.to_df();
let time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let file = std::fs::File::create(&(time.to_string().trim() + ".parquet")).unwrap();
println!("Wrote parquet file: {}", &(time.to_string().trim().to_owned() + ".parquet"));
// ERROR OCCURS HERE
// ERROR OCCURS HERE
// ERROR OCCURS HERE
ParquetWriter::new(file)
.with_compression(ParquetCompression::Snappy)
.with_statistics(true)
.finish(&mut df.collect().unwrap())
.expect("Failed to write parquet file");
main_vec.clear();
}
}.await;

How to set node properties as incrementing numbers, but resetting the increment when the value of a different property changes?

From the answer of How to set node properties as incrementing numbers?, I can set node properties as increasing numbers:
MATCH (n) where n.gid="A"
WITH collect(n) as nodes
WITH apoc.coll.zip(nodes, range(0, size(nodes))) as pairs
UNWIND pairs as pair
SET (pair[0]).id = pair[1]
return pair[0].gid, pair[0].id
╒═════════════╤════════════╕
│"pair[0].gid"│"pair[0].id"│
╞═════════════╪════════════╡
│"A" │0 │
├─────────────┼────────────┤
│"A" │1 │
├─────────────┼────────────┤
│"A" │2 │
├─────────────┼────────────┤
│"A" │3 │
├─────────────┼────────────┤
│"A" │4 │
├─────────────┼────────────┤
But since I have a list of gid: ["A", "B", "C", "D", ...], and I want to run through all the nodes, and each time the gid value changes the incrementing numbers reset. So the result would be:
╒═════════════╤════════════╕
│"pair[0].gid"│"pair[0].id"│
╞═════════════╪════════════╡
│"A" │0 │
├─────────────┼────────────┤
│"A" │1 │
├─────────────┼────────────┤
│"A" │2 │
├─────────────┼────────────┤
│... │... │
├─────────────┼────────────┤
│"A" │15 │
├─────────────┼────────────┤
│"B" │1 │
├─────────────┼────────────┤
│"B" │2 │
I use
MATCH (p) with collect(DISTINCT p.gid) as gids
UNWIND gids as gid
MATCH (n) where n.gid=gid
WITH collect(n) as nodes
WITH apoc.coll.zip(nodes, range(0, size(nodes))) as pairs
UNWIND pairs as pair
SET (pair[0]).id = pair[1]
return pair[0].name, pair[0].id
and it doesn't reset the number, i.e.
╒═════════════╤════════════╕
│"pair[0].gid"│"pair[0].id"│
╞═════════════╪════════════╡
│"A" │0 │
├─────────────┼────────────┤
│"A" │1 │
├─────────────┼────────────┤
│"A" │2 │
├─────────────┼────────────┤
│... │... │
├─────────────┼────────────┤
│"A" │15 │
├─────────────┼────────────┤
│"B" │16 │
├─────────────┼────────────┤
│"B" │17 │
Why is that?
The answer to the question "Why is that?" is that your cypher only results in a single list.
I think that when you split the lists by adding a n.gid on line 4
MATCH (p) with collect(DISTINCT p.gid) as gids
UNWIND gids as gid
MATCH (n) where n.gid=gid
// <<< do a "group by"
WITH n.gid AS gid,
collect(n) as nodes // <<< do a "group by"
WITH apoc.coll.zip(nodes, range(0, size(nodes))) as pairs
UNWIND pairs as pair
SET (pair[0]).id = pair[1]
return pair[0].name, pair[0].id
it could work.

How to draw methods inside self message methods for UML sequence diagrams

I'm trying to draw a sequence diagram for a code which looks like this:
Class Z:
method y()
Class X:
method w()
Class A inherits X & Z:
method b() {
calls y()
calls w()
}
A a;
a.b()
At first I thought that I should draw a self message method like this:
┌─┐
│A│
└┬┘
┌┴┐
│ │────┐
│ │ │ b()
│ │<───┘
│ │
│ │
└┬┘
┌┴┐
│A│
└─┘
but then I realised that this doesn't show how b() calls y(), so I considered this:
┌─┐ ┌─┐ ┌─┐
│A│ │Z│ │X│
└┬┘ └┬┘ └┬┘
┌┴┐ y() │ │
│ │ ─────────>│ │
│ │ │ │
│ │ w() │
│ │ ──────────────────────>│
└┬┘ │ │
│ return │
│<─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
┌┴┐ ┌┴┐ ┌┴┐
│A│ │Z│ │X│
└─┘ └─┘ └─┘
But then this one doesn't show b(). Is there a way I can draw this sequence diagram so that I can show both methods?
The call of b seems to be made out of something associated to a class, so let say the initial caller is not important using a found message, then y and w are applied on the instance of A executing b whatever these operations are inherited, so :
#bruno's answer is correct. He did not include the return message. Here is a diagram that I created with ZenUML (https://app.zenuml.com).
disclaimer: I am the author of ZenUML.

Groupby with sum on Julia Dataframe

I am trying to make a groupby + sum on a Julia Dataframe with Int and String values
For instance, df :
│ Row │ A │ B │ C │ D │
│ │ String │ String │ Int64 │ String │
├─────┼────────┼────────┼───────┼────────┤
│ 1 │ x1 │ a │ 12 │ green │
│ 2 │ x2 │ a │ 7 │ blue │
│ 3 │ x1 │ b │ 5 │ red │
│ 4 │ x2 │ a │ 4 │ blue │
│ 5 │ x1 │ b │ 9 │ yellow │
To do this in Python, the command could be :
df_group = df.groupby(['A', 'B']).sum().reset_index()
I will obtain the following output result with the initial column labels :
A B C
0 x1 a 12
1 x1 b 14
2 x2 a 11
I would like to do the same thing in Julia. I tried this way, unsuccessfully :
df_group = aggregate(df, ["A", "B"], sum)
MethodError: no method matching +(::String, ::String)
Have you any idea of a way to do this in Julia ?
Try (actually instead of non-string columns, probably you want columns that are numeric):
numcols = names(df, findall(x -> eltype(x) <: Number, eachcol(df)))
combine(groupby(df, ["A", "B"]), numcols .=> sum .=> numcols)
and if you want to allow missing values (and skip them when doing a summation) then:
numcols = names(df, findall(x -> eltype(x) <: Union{Missing,Number}, eachcol(df)))
combine(groupby(df, ["A", "B"]), numcols .=> sum∘skipmissing .=> numcols)
Julia DataFrames support split-apply-combine logic, similar to pandas, so aggregation looks like
using DataFrames
df = DataFrame(:A => ["x1", "x2", "x1", "x2", "x1"],
:B => ["a", "a", "b", "a", "b"],
:C => [12, 7, 5, 4, 9],
:D => ["green", "blue", "red", "blue", "yellow"])
gdf = groupby(df, [:A, :B])
combine(gdf, :C => sum)
with the result
julia> combine(gdf, :C => sum)
3×3 DataFrame
│ Row │ A │ B │ C_sum │
│ │ String │ String │ Int64 │
├─────┼────────┼────────┼───────┤
│ 1 │ x1 │ a │ 12 │
│ 2 │ x2 │ a │ 11 │
│ 3 │ x1 │ b │ 14 │
You can skip the creation of gdf with the help of Pipe.jl or Underscores.jl
using Underscores
#_ groupby(df, [:A, :B]) |> combine(__, :C => sum)
You can give name to the new column with the following syntax
julia> #_ groupby(df, [:A, :B]) |> combine(__, :C => sum => :C)
3×3 DataFrame
│ Row │ A │ B │ C │
│ │ String │ String │ Int64 │
├─────┼────────┼────────┼───────┤
│ 1 │ x1 │ a │ 12 │
│ 2 │ x2 │ a │ 11 │
│ 3 │ x1 │ b │ 14 │

Why isn't my object attribute populated?

Given this oversimplified XML file:
<Foo>Bar</Foo>
And this code which extracts the value for the Foo element:
use XML::Rabbit;
use Data::Dump::Tree;
class RunInfo does XML::Rabbit::Node {
has $.foo is xpath("/Foo");
}
sub MAIN ( $file! ) {
my $xml = RunInfo.new( file => $file );
dump $xml;
put "-----------------------";
put "Foo is $xml.foo()";
}
You'll see that the value for foo is Nil, even though the output shows Foo is Bar:
.RunInfo #0
├ $.foo = Nil
├ $.context is rw = .XML::Document #1
│ ├ $.version = 1.0.Str
│ ├ $.encoding = Nil
│ ├ %.doctype = {0} #2
│ ├ $.root = .XML::Element #3
│ │ ├ $.name is rw = Foo.Str
│ │ ├ #.nodes is rw = [1] #4
│ │ │ └ 0 = .XML::Text #5
│ │ │ ├ $.text = Bar.Str
│ │ │ └ $.parent is rw = .XML::Element §3
│ │ ├ %.attribs is rw = {0} #7
│ │ ├ $.idattr is rw = id.Str
│ │ └ $.parent is rw = .XML::Document §1
│ ├ $.filename = example.xml.Str
│ └ $.parent is rw = Nil
└ $.xpath is rw = .XML::XPath #9
├ $.document = .XML::Document §1
└ %.registered-namespaces is rw = {0} #11
-----------------------
Foo is Bar
(Disclaimer: I came across this behavior today in my code, so I wrote it up Q & A style. Other answers welcome.).
By the way, here are links to XML::Rabbit and Data::Dump::Tree.
It is lazy, like many things in Perl 6. In other words, it intentionally doesn't waste time figuring out what the foo attribute is unless you ask for it. This is an optimization that avoids consuming computational resources unless you need them.
If you dump the data structure after calling the foo method, you'll see that it is populated in the data dump:
use XML::Rabbit;
use Data::Dump::Tree;
class RunInfo does XML::Rabbit::Node {
has $.foo is xpath("/Foo");
}
sub MAIN ( $file! ) {
my $xml = RunInfo.new( file => $file );
put "Foo is $xml.foo()";
dump $xml;
}
Foo is Bar
.RunInfo #0
├ $.foo = Bar.Str
├ $.context is rw = .XML::Document #1
│ ├ $.version = 1.0.Str
│ ├ $.encoding = Nil
│ ├ %.doctype = {0} #2
│ ├ $.root = .XML::Element #3
│ │ ├ $.name is rw = Foo.Str
│ │ ├ #.nodes is rw = [1] #4
│ │ │ └ 0 = .XML::Text #5
│ │ │ ├ $.text = Bar.Str
│ │ │ └ $.parent is rw = .XML::Element §3
│ │ ├ %.attribs is rw = {0} #7
│ │ ├ $.idattr is rw = id.Str
│ │ └ $.parent is rw = .XML::Document §1
│ ├ $.filename = example.xml.Str
│ └ $.parent is rw = Nil
└ $.xpath is rw = .XML::XPath #9
├ $.document = .XML::Document §1
└ %.registered-namespaces is rw = {0} #11
This is not the result of a built-in Perl 6 feature, but rather something the XML::Rabbit module does.
That module provides the is xpath trait, and makes sure that at class composition time, any attribute which has that trait applied gets its accessor method overridden with a custom one.
The custom accessor method calculates and sets the value for the attribute the first time it is called, and on subsequent calls simply returns the value that's now already stored in the attribute.
The custom accessor method is implemented as follows (taken from the module's source code with parts elided):
method (Mu:D:) {
my $val = $attr.get_value( self );
unless $val.defined {
...
$val = ...;
...
$attr.set_value( self, $val );
}
return $val;
}
Here, $attr is the Attribute object corresponding to the attribute, and was retrieved prior to installing the method using the Meta-Object Protocol (MOP).
The Data::Dump::Tree module, in turn, doesn't use the accessor method to fetch the attributes's value, but rather reads it directly using the MOP.
Therefore it sees the attribute's value as Nil if has not yet been set because the accessor was not yet called.