#How could I make a function repeat an fallible action a max number of retries?

1 messages · Page 1 of 1 (latest)

vague galleon
#

Hi! I've been trying to write an HTTP server in Zig but the pg driver I'm using sometimes fails and I would like to retry the queries a max number of times before returning with the last error of the operation.

The solution so far that I could come up with is:

/// Retries a given fallible operation a max number of times.
pub fn retryOperation(comptime ReturnValue: type, config: RetryConfig, ctx: type) !ReturnValue {
    if (config.max_retries < 1) {
        return error.MaxRetriesReached;
    }

    return ctx.run() catch |err| {
        uwu_log.logErr("Error in operation!").err(err).src(@src()).log();
        return retryOperation(ReturnValue, .{ .max_retries = config.max_retries - 1 }, ctx);
    };
}

Later I can use it like this:

/// Common logic for managing errors inside DB queries in transactions.
/// This method responds to the HTTP request, so no longer action is needed.
pub fn manageTransactionError(r: *const zap.Request, conn: *pg.Conn, err: anyerror) void {
    manageQueryError(r, conn, err);
    const Ctx = struct {
        var connection: *pg.Conn = undefined;

        pub fn run() !void {
            return connection.rollback();
        }
    };
    Ctx.connection = conn;
    utils.db.retryOperation(void, .{}, Ctx) catch unreachable;
}

So far so good.

#

The error occurs when I try to make a wrapper for the pg.QueryRow type:

/// Retries a row query operation a max number of times according to the specified config.
pub fn retryRowQuery(config: RetryConfig, conn: *pg.Conn, query: []const u8, params: anytype) !?pg.QueryRow {
    const ParamsType = @TypeOf(params);
    const Ctx = struct {
        var connection: *pg.Conn = undefined;
        var sql: []const u8 = undefined;
        var values: ParamsType = undefined;

        pub fn run() !?pg.QueryRow {
            return connection.row(sql, values);
        }
    };

    Ctx.connection = conn;
    Ctx.sql = query;
    Ctx.values = params;

    return retryOperation(!?pg.QueryRow, config, Ctx);
}

When I try to compile it fails with:

install
└─ install backend
   └─ zig build-exe backend Debug native 1 errors
src/zigLib/utils/db.zig:32:28: error: expected type 'bool', found 'type'
    return retryOperation(!?pg.QueryRow, config, Ctx);
                           ^~~~~~~~~~~~
referenced by:
    create_task: src/zigLib/ws/task.zig:64:43
    on_message: src/zigLib/ws/root.zig:204:48
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
error: the following command failed with 1 compilation errors:

db.zig:32:28 is the final line of the retryRowQuery from before.

What am I doing wrong? Why does the error say: expected type 'bool', found 'type'?

Any help would be greatly appreciated!

restive igloo
#

it looks like the ! is being parsed as a unary not and expecting the rest of the expression to be a boolean

#

does wrapping it in parens work, maybe? return retryOperation((!?pg.QueryRow), ...)

#

oh, actually, I suppose the error in the error union can't be inferred here

#

you likely need to name the error type

vague galleon
#
install
└─ install backend
   └─ zig build-exe backend Debug native 1 errors
src/zigLib/utils/db.zig:32:29: error: expected type 'bool', found 'type'
    return retryOperation((!?pg.QueryRow), config, Ctx);
#

nice try, but same error haha

vague galleon
languid hamlet
#

inferred error sets are only allowed in function return types, theyre not an actual type

vague galleon
#

ok, turns out the library I'm using uses inferred error sets in all the functions, but the one I'm using is generic

#

when I try to compile it fails with:

install
└─ install backend
   └─ zig build-exe backend Debug native 1 errors
src/zigLib/utils/db.zig:35:26: error: unable to resolve inferred error set of generic function
    return retryOperation(ConnQueryErrors, config, Ctx);
           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/elrohirgt/.cache/zig/p/1220795dd7be5bb28a29b7cf1a46233b0c370c158d7f4183c35db27f2b7db9564358/src/conn.zig:222:9: note: generic function declared here
    pub fn row(self: *Conn, sql: []const u8, values: anytype) !?QueryRow {
    ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    create_task: src/zigLib/ws/task.zig:64:43
    on_message: src/zigLib/ws/root.zig:204:48
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
error: the following command failed with 1 compilation errors:
#

Is there any other way to get the type? Aside from doing this:

const ConnQueryErrors = @typeInfo(@TypeOf(pg.Conn.row)).Fn.return_type orelse unreachable;
restive igloo
#

that's what I'd do, tbh

#

you could potentially anyerror it unless you care what the type is, or name your own error set which contains all the errors you expect (you'll fail at the callsite if the expanded generic could return one not in your set)

vague galleon
#

hmmm... I see. I'll try the custom error set, hope the compiler tells me the error unions

vague galleon
#

damn... It doesn't:

install
└─ install backend
   └─ zig build-exe backend Debug native 1 errors
src/zigLib/utils/db.zig:37:26: error: unable to resolve inferred error set
    return retryOperation(ConnQueryErrors!?pg.QueryRow, config, Ctx);
           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    create_task: src/zigLib/ws/task.zig:64:43
    on_message: src/zigLib/ws/root.zig:204:48
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
error: the following command failed with 1 compilation errors:

😭

#

I guess I'll have to use anyerror

ashen moss
vague galleon
#

Damn! Thanks for the response! Unfortunetly is really late for me, will definitively check it out tomorrow tho!

#

I couldnt get the anyerror solution to work, I'll probably have coding nightmares 💀

restive igloo
vague galleon
ashen moss
vague galleon
#

But when I try to use it, it fails with:

src/zigLib/utils/db.zig:75:40: error: no field named 'row' in struct 'conn.Conn'
return retryOperation(config, conn.row, .{ query, params });

conn.row is a function

ashen moss
#

thats using method syntax. The real function is conn.Conn.row. Zig used to support method-syntax in call-builtin but that required a hacky concept named BoundFn. Now, I think you just do it manually: retryOperation(config, conn.Conn.row, .{ conn, query, params })

vague galleon
#

ofc! That makes sense! But it fails because conn.Conn.row is generic!

#

src/zigLib/utils/db.zig:75:26: error: unable to resolve inferred error set of generic function
return retryOperation(config, pg.Conn.row, .{ conn, query, params });
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/elrohirgt/.cache/zig/p/1220795dd7be5bb28a29b7cf1a46233b0c370c158d7f4183c35db27f2b7db9564358/src/conn.zig:222:9: note: generic function declared here
pub fn row(self: *Conn, sql: []const u8, values: anytype) !?QueryRow {

ashen moss
#

what about making its return type instead: @TypeOf(@call(.auto, f, args))

vague galleon
#

gonna try it

#

ok I thinks that works! I'm gonna solve all the other compiler errors that came by changing the API and keep you informed if it doesn't work in the end

#

just to be clear, would having that extra @call actually call my function a second time? Or is it just done at compile time or something like that?

ashen moss