DistributedFactorGraphs.jl icon indicating copy to clipboard operation
DistributedFactorGraphs.jl copied to clipboard

Standardize DFG Graph CRUD return types

Open Affie opened this issue 6 years ago • 51 comments

Decide on and standardize behavior on return and exceptions for CRUD

Affie avatar Oct 03 '19 15:10 Affie

FROM SLACK

Currently the behavior of GraphsDFG and CloudDFG is somewhat different. We want it consistent, but i would not want the cloud breaking julia, but rather the ability to retry

dehann avatar Oct 06 '19 23:10 dehann

I like the cloud graphs addVariable in that it does not error, but not the automatic update if it exists. I would propose the behavior is to @error on exists and return false.

dehann avatar Oct 06 '19 23:10 dehann

Update does not seem to change the graph structure in in-memory types which might be a bug? It errors on GraphsDFG but creates with @warn on CloudDFG. Create with @warn or @error seems logical

dehann avatar Oct 06 '19 23:10 dehann

Delete might just return nothing with @error agian?

dehann avatar Oct 06 '19 23:10 dehann

Hi Johan consistent definitely good, cloud @error with false a good start. The rerun false might be under question at some point since initially addVariable used to return the variable object. Maybe escalate the in memory to also be @error. Probably need a hard decision addVariable return type. Should limit to just one type I think.The retry can work well, although there is a discussion on whether user or driver has that responsibility. If it’s small then maybe a keyword argument to disable retry?Not sure on the return type for delete, that could perhaps be nice if the DFGVariable is returned, or empty if failed?

dehann avatar Oct 06 '19 23:10 dehann

On retry i would say user responsibility, auto retry can maybe be implemented in RoME. What I meant was, rather return a false or nothing with an @error or @warn than throwing an exception that stops julia.

dehann avatar Oct 06 '19 23:10 dehann

Agree not to throw exception. I’d suggest that that there is only one return type from the function for performance, but somehow indicates success or failure.

dehann avatar Oct 06 '19 23:10 dehann

On addVariable!: IIF returns the variable, DFG true/false. What is preferred. I would think if the function name is the same it should have the same behavior.

dehann avatar Oct 06 '19 23:10 dehann

definitely only one return type, but a Nothing Union eg. Union{Nothing, DFGVariable} might also be acceptable as the performance is not impacted that much since julia 0.7

dehann avatar Oct 06 '19 23:10 dehann

Right, union types thing... theres something I’m not sure about yet. There is a macro type_warn which shows type instability which is slow. Do return Union in functions skip dynamic type inference — will be great if that’s the case. Sounds like 0.7 was able to do that..? At some point I’ll try the type_warn thing to see if Union return is stable, having a hard time understanding how that would be the case though. As long as things are type stable I think it would be good?

dehann avatar Oct 06 '19 23:10 dehann

Its used in julia’s code base, eg findfirst. I kind of remember reading that small type unions were now fast, but that might have been in containers.

dehann avatar Oct 06 '19 23:10 dehann

Found it: https://julialang.org/blog/2018/06/missing#fnref:splitting

dehann avatar Oct 06 '19 23:10 dehann

Oh cool, okay thanks. Does this not also solve the dense adjacency matrix problem — ie Should use Union with isbits types like Missing. Not to replace sparse. I mean when going between sparse and dense representations.It does look like references are bad. So a Union{Missing, DFGVariable} would still be bad? But a Union{Missing, Bool} would be good... if I read that right. It’s immutable types that the compiler can optimize.

dehann avatar Oct 06 '19 23:10 dehann

looks like Union{Missing, T} and Union{Nothing, T} would then be optimized in the same way? Preference as to which type? At the moment we're using a lot of Union{Nothing, T}

dehann avatar Oct 06 '19 23:10 dehann

General design pattern should be exception throwing - standard across languages - if we use the return checking approach we force the user to do the work. I get that Julia isn't good with exception handling yet (for some reason, hasn't stuck in Julia) , so let's go with the return code approach. Sound like a plan?

dehann avatar Oct 06 '19 23:10 dehann

just a consideration: as we introduce functions that talk to databases etc., we will end up trapping the potential exceptions in the addVariable function, and returning true/false, which may make it more difficult for users to debug why it failed (does the variable already exists? is it a comms issue? etc.)

dehann avatar Oct 06 '19 23:10 dehann

E.g. using the return true/false approach. If this works we can make the driver changes: https://github.com/JuliaRobotics/DistributedFactorGraphs.jl/pull/136

dehann avatar Oct 06 '19 23:10 dehann

Hi, I think the point of that blog is Union nothing is bad but Union Missing is good. Not 100% sure but at least Union Missing is good.I think try catch exception handling is good. Julia does the job, don’t think there is an issue with that. My previous hold up with try statements was precisely the same issue of a graceful failure or one sentence error message but without reporting the call stack. I’d prefer to use try catch but then just log the stack trace and message properly so we can debug more easily. Try catch should not be used for duplicate adds or delete a missing value, right?So sounds like 2 questions then, if I have this right and feel free to disagree:

  1. What is return types for graph cruds — considering missings? Suggestion is Union{Missing, <: isbits}...
  2. Missing is not an exception error but other errors should be caught internally vi try catch with full stack logs? Currently only some of the DB code has this style...

dehann avatar Oct 06 '19 23:10 dehann

My question on IIF/RoME got missed, so repeating: On addVariable!: IIF returns the variable, DFG true/false. What is preferred. I would think if the function name is the same it should have the same behavior. I currently use the IIF/RoME variable that is returned. The options for addVariable!:

  • Inconstant behavior; IIF return variable, DFG true/false or true exception.
  • return true/false, use addVariable! then getVariable! -> I think this will have a big impact downstream.
  • return variable everywhere, but question becomes how to deal with failure.

Affie avatar Oct 07 '19 08:10 Affie

My understanding of the blog is two parts:

  1. functions: any 'small unions' are acceptable
  2. arrays: only bit types.

returning nothing will automatically break the users code if a dfg variable is expected.

perhaps a flag that either throws exception or @error with nothing return?

Affie avatar Oct 07 '19 08:10 Affie

https://docs.julialang.org/en/v1/manual/style-guide/index.html#Don't-overuse-try-catch-1

dehann avatar Oct 07 '19 13:10 dehann

My question on IIF/RoME got missed, so repeating: On addVariable!: IIF returns the variable, DFG true/false. What is preferred. I would think if the function name is the same it should have the same behavior. I currently use the IIF/RoME variable that is returned. The options for addVariable!:

  • Inconstant behavior; IIF return variable, DFG true/false or true exception.
  • return true/false, use addVariable! then getVariable! -> I think this will have a big impact downstream.
  • return variable everywhere, but question becomes how to deal with failure.

dehann avatar Oct 07 '19 13:10 dehann

~~HI Please continue this discussion in the issue thread~~. Question did not get missed sorry -- answer is behavior should be the same and we now need to make a design decision.

dehann avatar Oct 07 '19 13:10 dehann

I am personally not a big fan of returning union types yet, but chewing on it.

dehann avatar Oct 07 '19 13:10 dehann

Hi @GearsAD , do you have strong preference on return type for graph CRUD -- think we going to vote on this to resolve? My feeling is we should return DFGVariable/DFGFactor type, but that leaves the issue of what to do when error occurs.

  • I don't know if Union{Missing, <:Node} is the the most performant and can create type instability, since it is not a 'small union'.
  • Error handling inside the function is tangential issue, but there are two categories (do or dont do try catch inside CRUD):
    • Type 1: Functional or syntactic errors
    • Type 2: 'not found' / duplicate / invalid value errors / cannot execute

Alternative Suggestion for CRUD Return

How about always returning a Tuple{Bool, <:DFGNode}. The bool flag states success and the second position is the relevant response. ie

fg = initfg()
flag, var = addVariable!(fg, :x1, Pose2)
@assert flag
flag, var__ = addVariable!(fg, :x1, Pose2)
@assert !flag

# var__ should be empty container, but specifically in this case could also be previous var

Only do try catch on CRUD Type 2 errors?

xref #144

dehann avatar Oct 07 '19 14:10 dehann

Summary of discussion with @GearsAD (Please feel free to update if I misunderstood something): -add...! adds. -update...! updates, if it exists, adds (with @warn) if not. -detete...! -get...! All throws exception on fail or returns variable/factor on success. (to fit with RoME/IIF)

Forgot about the flag. Its need will depend on where the error handling is done. I think it will be most usefull in the external dfg types (cloud, file, etc) for example:

function addVariable!(dfg::CloudGraphsDFG, variable::DFGVariable)::Bool
    if exists(dfg, variable)
        if getErrorHandlingFlag(dfg) #returns dfg.throwExceptionOnError 
            error("Variable '$(variable.label)' already exists in the factor graph")
        else
            @error "Variable '$(variable.label)' already exists in the factor graph"
            # preverably with a stack trace, but since you can set throwExeptionOnError true it should be easy to debug
        end
        return false
    end
    ...
    ...
    return true
end

With the return type pending this decision

Affie avatar Oct 07 '19 14:10 Affie

small union: This optimization applies to all Unions of a small number of types, whether they are bits types or not. “Small” is defined by the MAX_UNION_SPLITTING constant, which is currently set to 4

Affie avatar Oct 07 '19 14:10 Affie

right, so i'm guessing DFGVariable or DFGFactor would exceed MAX_UNION_SPLITTING?

dehann avatar Oct 07 '19 14:10 dehann

All throws exception on fail or returns variable/factor on success. (to fit with RoME/IIF)

RoME/IIF can change as needed. Starting a new thread for that here #144

dehann avatar Oct 07 '19 14:10 dehann

My understanding Union{Nothing, Any} -> bad. Union{Nothing, DFGVariable} -> ok, spit in 2.

Affie avatar Oct 07 '19 14:10 Affie