# Fixing invalidation - what would an expert do?

**URL:** <https://discourse.julialang.org/t/fixing-invalidation-what-would-an-expert-do/98484>\
**Category:** General Usage\
**Created:** [May 8, 2023, 3:13pm UTC](https://discourse.julialang.org/t/fixing-invalidation-what-would-an-expert-do/98484 "2023-05-08T15:13:08Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![svilupp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/svilupp/32/34933_2.png) [@svilupp](https://discourse.julialang.org/u/svilupp)\
**Post date:** [May 8, 2023, 3:13pm UTC](https://discourse.julialang.org/t/fixing-invalidation-what-would-an-expert-do/98484/1 "2023-05-08T15:13:08Z")

</div>

First of all, thank you for the awesome tooling around this. I appreciate there isn’t a straightforward answer to each case.

I’ve re-read and re-watched a few times:

- [the blog post on invalidations](https://julialang.org/blog/2020/08/invalidations/) and the [post](https://discourse.julialang.org/t/finding-and-fixing-invalidations-now-everyone-can-help-reduce-time-to-first-plot/45598/8)
- [SnoopCompile.jl docs on invalidations](https://timholy.github.io/SnoopCompile.jl/stable/snoopr/#Fixing-invalidations)
- [analyzing and fixing invalidations](https://www.youtube.com/watch?v=7VbXbI6OmYo&ab_channel=TimHoly)
- and the discussion on [invalidations in GMT](https://discourse.julialang.org/t/invalidations-findings-from-a-gmt-case/92420/61)

It all makes sense when explained, but I’m struggling to convert my understanding into actions on real-life examples (eg, when to `invokelatest`, add concrete type annotations to caller body, open an issue with an upstream pkg or even with Julia Base)

Is there a resource that I missed / that I should look into?

**Could you perhaps comment on what you would do in the following cases?**

I’ve been looking at precompiling some Genie/Stipple-based apps and noticed a few cases where I didn’t know what the right next step would be.

Using the standard snippet (awesome idea to have this copy&paste-able snippet!):

```julia
using SnoopCompileCore
invalidations = @snoopr begin
    using GenieFramework
end
using SnoopCompile
length(uinvalidated(invalidations))
trees = invalidation_trees(invalidations)
methinvs = trees[end];
root = methinvs.backedges[end]
ascend(root)

```

**1) Example with unlock(io)**  
On the last record (most invalidations), I get

```julia
methinvs = trees[end]
 inserting unlock(io::GarishPrint.GarishIO) @ GarishPrint ~/.julia/packages/GarishPrint/214Ip/src/io.jl:153 invalidated:
   backedges: 1: superseding unlock(::IO) @ Base io.jl:27 with MethodInstance for unlock(::IO) (385 children)
   6 mt_cache

```

I hit the ascend and jump to the last concrete call

```julia
julia> ascend(root)
Choose a call for analysis (q to quit):
     unlock(::IO)
       print(::IO, ::String, ::String)
         println(::IO, ::String)
 > println(::String)
             deep_clean!(::Pkg.Resolve.Graph)
               #simplify_graph!#117(::Bool, ::typeof(Pkg.Resolve.simplify_graph!), ::Pkg.Resolve.Graph, ::Set{Int64})
                 simplify_graph!(::Pkg.Resolve.Graph, ::Set{Int64})
                   simplify_graph!(::Pkg.Resolve.Graph)
                     trigger_failure!(::Pkg.Resolve.Graph, ::Vector{Int64}, ::Tuple{Int64, Int64})
v _resolve(::Pkg.Resolve.Graph, ::Nothing, ::Nothing)

# that shows 
println(xs...) @ Base ~/.julia/juliaup/julia-1.9.0-rc3+0.aarch64.apple.darwin14/share/julia/base/coreio.jl:4
4 println(xs::Tuple{String}...)::Core.Const(nothing) = println(stdout::Any, xs::Tuple{String}...)

```

It tells me that the inference fails because Any comes from printing into `stdout`.  
That’s confusing because:

- it’s extremely common, so I would expect problems everywhere
- I thought `stdout` is a concrete type (Base.TTY)
- the inference correctly identifies that all the returns are nothing

Perhaps I shouldn’t be looking at the backedges, so I looked at the code that inserted the new method, but that’s defined on a concrete type.  
It doesn’t feel like “stomping out the Any type” applies here.

**2) Example with peek(io)**  
After the failure with the last “tree”, I move to the one before last

```julia
julia> ascend(trees[end-1].backedges[end])
Choose a call for analysis (q to quit):
     peek(::IO)
       read(::IO, ::Type{Char})
         iterate(::Base.ReadEachIterator{Char}, ::Nothing)
           iterate(::Base.ReadEachIterator{Char})
 > #readuntil#433(::Bool, ::typeof(readuntil), ::IO, ::Char)
               readuntil(::IO, ::Char)
                 #readuntil#411(::Base.Pairs, ::typeof(readuntil), ::Base.AbstractPipe, ::Char)
                   readuntil(::Base.AbstractPipe, ::Char)
                     #readuntil#411(::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, ::typeof(re
v readuntil(::Base.Process, ::Char)

```

So I descend into the last line with Base.Process, as it’s the last concrete call and I ultimately end up with:

```julia
readuntil((pipe_reader(io::Base.AbstractPipe)::Any::IO::Type{IO})::IO, arg::Char; kw::Base.Pairs{Symbol,…

```

I’m bit lost because I have no idea how to concretize what will come out of that pipe.  
That’s another failure on my part.

_**3) Example with convert(T,…)**_  
So I clearly have no idea when it comes to IO, so I figured to jump to `convert` function next, because I have noticed that it features in every single attempt I’ve made at analyzing invalidations (across many different packages).

So I jump into:

```julia
 inserting convert(::Type{T}, x::EzXML.ReaderType) where T<:Integer @ EzXML ~/.julia/packages/EzXML/ZNwhK/src/streamreader.jl:54 invalidated:
   backedges: 1: superseding convert(::Type{T}, x::Number) where T<:Number @ Base number.jl:7 with MethodInstance for convert(::Type{Int64}, ::Integer) (22 children)
              2: superseding convert(::Type{T}, x::Number) where T<:Number @ Base number.jl:7 with MethodInstance for convert(::Type{UInt64}, ::Integer) (28 children)
              3: superseding convert(::Type{T}, x::Number) where T<:Number @ Base number.jl:7 with MethodInstance for convert(::Type{UInt64}, ::Integer) (1 children)
              4: superseding convert(::Type{T}, x::Number) where T<:Number @ Base number.jl:7 with MethodInstance for convert(::Type{UInt64}, ::Integer) (7 children)
              5: superseding convert(::Type{T}, x::Number) where T<:Number @ Base number.jl:7 with MethodInstance for convert(::Type{Int8}, ::Integer) (40 children)
              6: superseding convert(::Type{T}, x::Number) where T<:Number @ Base number.jl:7 with MethodInstance for convert(::Type{UInt64}, ::Integer) (10 children)
              7: superseding convert(::Type{T}, x::Number) where T<:Number @ Base number.jl:7 with MethodInstance for convert(::Type{Int64}, ::Integer) (68 children)

```

And descript scrolling to the bottom of the list, I haven’t found any line where the “reds” (abstract types) start:

```julia
julia> ascend(trees[end-4].backedges[end])
Choose a call for analysis (q to quit):
^ to_index(::Integer)
         to_index(::Vector{Int64}, ::Integer)
           _to_indices1(::Vector{Int64}, ::Tuple{}, ::Integer)
             to_indices(::Vector{Int64}, ::Tuple{}, ::Tuple{Integer})
               to_indices(::Vector{Int64}, ::Tuple{Integer})
                 setindex!(::Vector{Int64}, ::Int64, ::Union{Integer, CartesianIndex})
                   sortperm_int_range(::Vector{T} where T<:Integer, ::Any, ::Any)
 > #_sortperm#33(::Base.Sort.MissingOptimization{Base.Sort.BoolOptimization{Base.Sort.Small{10
                       kwcall(::NamedTuple{(:alg, :order, :scratch), Tuple{Base.Sort.MissingOptimization{Base.So
v #sortperm#32(::Base.Sort.MissingOptimization{Base.Sort.BoolOptimization{Base.Sort.Small

...

 > to_index(::Vector{Symbol}, ::Number)
           _to_indices1(::Vector{Symbol}, ::Tuple{Base.OneTo{Int64}}, ::Number)
             to_indices(::Vector{Symbol}, ::Tuple{Base.OneTo{Int64}}, ::Tuple{Number})
               to_indices(::Vector{Symbol}, ::Tuple{Number})
                 getindex(::Vector{Symbol}, ::Number)
                   maybeview(::Vector{Symbol}, ::Union{Number, Base.AbstractCartesianIndex})

```

Apologies for the long post, but would you have any tips / advice how to:

- proceed in the above cases?
- identify which cases are “easier” to fix for people learning about invals. (eg, “avoid all convert()”, “avoid io”, etc.)

Thank you!

---

<div class="post-metadata">

**Author:** ![ranocha](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ranocha/32/35588_2.png) [@ranocha](https://discourse.julialang.org/u/ranocha)\
**Post date:** [May 8, 2023, 3:26pm UTC](https://discourse.julialang.org/t/fixing-invalidation-what-would-an-expert-do/98484/2 "2023-05-08T15:26:01Z")

</div>

> [@svilupp](#):
>
> - I thought `stdout` is a concrete type (Base.TTY)
> - the inference correctly identifies that all the returns are nothing

`stdout` is a global variable that is just guaranteed to be an `IO` - which is an abstract type. You can change `stdout`, e.g.,

```julia-repl
julia> redirect_stdout(devnull) do
           @info "Printing" typeof(stdout) typeof(stderr)
       end
┌ Info: Printing
│ typeof(stdout) = Base.DevNull
└ typeof(stderr) = Base.TTY

```

It’s not only about the inference of return types but also about the methods that need to be called. Since `unlock(::IO)` appears to be used and a new method of `unlock` is added, invalidations occur.

---

<div class="post-metadata">

**Author:** ![svilupp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/svilupp/32/34933_2.png) [@svilupp](https://discourse.julialang.org/u/svilupp)\
**Post date:** [May 8, 2023, 5:18pm UTC](https://discourse.julialang.org/t/fixing-invalidation-what-would-an-expert-do/98484/3 "2023-05-08T17:18:35Z")

</div>

Oh, right! I can see why invalidations happen.

What could be the solution? (If there is even one)  
It seems like something that will affect any package that implements its own IO.

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [May 9, 2023, 9:47am UTC](https://discourse.julialang.org/t/fixing-invalidation-what-would-an-expert-do/98484/4 "2023-05-09T09:47:30Z")

</div>

> [@svilupp](#):
>
> It tells me that the inference fails because Any comes from printing into `stdout`.

That’s not a problem if the result isn’t used. Inference failures only matter when Julia can’t predict which MethodInstance to call, and an unused output is never an argument in a call.

One other thing to note: anything under `deep_clean!` is in Pkg. While it’s great to make Julia and its stdlibs more resistant to invalidation (I’ve spent a lot of time doing that myself), do keep in mind that it may be irrelevant for specific tasks you want to perform. That is to say, this invalidation may affect you the next time you, say, update your packages in the same session, but it may not affect GenieFramework code unless GenieFramework interacts with Pkg.

> So I descend into the last line with Base.Process, as it’s the last concrete call

IO objects are tough this way:

```julia
julia> fieldnames(Base.Process)
(:cmd, :handle, :in, :out, :err, :exitcode, :termsignal, :exitnotify)

julia> fieldtypes(Base.Process)
(Cmd, Ptr{Nothing}, IO, IO, IO, Int64, Int32, Base.GenericCondition{Base.Threads.SpinLock})

```

The specific IO subtype is not typically specialized on, and that’s what sets up the invalidation you’re seeing. This is a very difficult invalidation to fix. You might be better off with `PrecompileTools.@recompile_invalidations`.

> `sortperm_int_range(::Vector{T} where T<:Integer, ::Any, ::Any)`

There seems to possibly be some missing info about the caller here? (The `...`) Whatever is creating the `Vector{<:Integer}` is likely the problem.
