1

Okay, so I'm stumped. Here's the thing I can't seem to solve: In making calls a public API, the first thing required is an access token -- let's call my method get_access_token(). Once I've got the access token I will use it to get data from the public API. Now, the token expires after a certain number of seconds, that number being provided with the access token. So, the idea is to set up some sort of timer that will refresh the access token by calling get_access_token() again once the previous token expires, so that I can continue to get data from the API, always with a valid access token.

I've tried using:

  • alarm
  • AnyEvent
  • Time::HiRes qw(setitimer ITIMER_VIRTUAL time)

But I can't get any of these to work. I'm not wedded to any particular approach; I know I could solve it with a simple "check every so often to see if the access token is about to expire" using time() and arithmetic, but I thought I'd try something a little more sophisticated. Evidently too sophisticated for me!

Yes, I know that usually one posts code, but obviously my code is all crap, so really I'm just looking for some pointers about how best to approach this particular use case. Any help would be appreciated.

Edit

For @ThisSuitIsBlackNot, here's the alarm test (no actual API calls made). The alarm fires once but never again:

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

use Time::HiRes;

my $timeout = 7;
my $counter = 0;

my $rv = wrapper();
say $rv;

exit;


### subroutines

sub getAccessToken {
    return localtime(time());
}

sub getSomeIDs {
    my $accessToken = shift;

    say "getting IDs: timeout == $timeout";

    eval {
        local $SIG{'ALRM'} = sub { wrapper(); };
        alarm($timeout);
        while (1) {
            if ($counter == 25) { return; }
            say "[ $accessToken ] $counter";
            $counter = $counter + 1;
            alarm_resistant_sleep(1);
        }
        alarm(0);
    };
    alarm(0);

    return "counter at $counter";
}

sub wrapper {
    my $at = getAccessToken();
    return getSomeIDs($at);
}

sub alarm_resistant_sleep {
    my $end = Time::HiRes::time() + shift();
    for (;;) {
        my $delta = $end - Time::HiRes::time();
        last if $delta <= 0;
        select(undef, undef, undef, $delta);
    }
}

And here's the attempt to use AnyEvent. Originally I didn't use the closure thing, but read another post that suggested doing so (still didn't work):

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

use AnyEvent;

my $accessToken;
my $timeout = 7;
my $cv = AE::cv;

my $counter = 0;

my $f1 = makeClosure(\&refreshAccessToken);
my $f2 = makeClosure(\&getSomeIDs);

$f1->();
$f2->();
$cv->recv();

exit;


### subroutines

sub makeClosure {
    my $sub = shift;
    return $sub;
}

sub getAccessToken {
    $accessToken = localtime(time());
}

sub refreshAccessToken {
    my $t1; $t1 = AE::timer 0, $timeout,
        sub {
            say "callback called";
            $timeout--;
            $accessToken = localtime(time());
            #getAccessToken();
            undef $t1;
        };
    say "calling refreshAccessToken()";
}

sub getSomeIDs {
    my $t2; $t2 = AE::timer 0, 1,
        sub {
            if ($counter >= 16) { undef $t2; }
            say "[ $accessToken ] $counter";
            $counter = $counter + 1;
        };
    say "getting IDs: timeout == $timeout";
}

And, finally, here's the Time::HiRes test. Again, this code went through many modifications trying to get it to work. The ridiculous value tested against $counter was simply to see if the timer was called at any point, which it was not:

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

use Time::HiRes qw(setitimer ITIMER_VIRTUAL time);

my $accessToken;

my $counter = 0;

$SIG{VTALRM} = \&refreshAccessToken;
setitimer(ITIMER_VIRTUAL, 1, 5);

Time::HiRes::sleep(2);

getAccessToken();
getSomeIDs();

exit;


### subroutines

sub refreshAccessToken {
    my $timeout = getAccessToken();
    local $SIG{VTALRM} = \&refreshAccessToken;
    setitimer(ITIMER_VIRTUAL, $timeout, $timeout);
    die;
}

sub getAccessToken {
    say "getting access token";
    $accessToken = localtime(time());
    return 2;
}

sub getSomeIDs {

    my $loop = 1;

    #say "getting IDs: timeout == $timeout";

    while ($loop) {
        if ($counter >= 1600000000) { $loop = 0; }
        say "[ $accessToken ] $counter";
        $counter = $counter + 1;
    };
}
5
  • You only call alarm($timeout); once, so the alarm only fires once. You need to call it every time you get a new access token. Commented Feb 26, 2016 at 18:13
  • I thought it would be called multiple times because $SIG{ALRM} is set to call the wrapper() subroutine, which includes a call to getSomeIDs(), which calls alarm($timeout). Commented Feb 26, 2016 at 18:28
  • I just tried adding alarm($timeout) in the getAccessToken() subroutine -- it did not change the behaviour in the slightest. Commented Feb 26, 2016 at 18:40
  • What sort of duration is your authorisation valid for? I would be inclined to request a code only when I know I don't have one (i.e. it's the first time I've used the API this session) or when I make a request that returns an "unauthorised" status. Trying to do this by timing will result in all kinds of problems like the code being valid when the request is sent but not when the server receives it, which you still have to handle Commented Feb 26, 2016 at 20:53
  • Well, it /may/ be for an hour (3600 secs) but given that the token request will return a token and an expire time, my guess is that the API reserves the right to set whatever time they want. The good thing is you know with each refreshed token what that time is. An earlier answer (now deleted for some reason) also suggested the "wait for token expiration notice" approach, but if you do that you get a penalty and have to wait a certain amount of time (specified in the error response) before you can access the API again. Commented Feb 26, 2016 at 21:17

2 Answers 2

2

This is trivial to solve, if expiration time is given by the API. This is an approach using time and simple arithmetic.

sub get_access_token {
    # some token generation code
    return ( $token, $expire_time );
}

my ( $token, $expire_time ) = get_access_token;
my $expire_timestamp = time + $expire_time;

while (1) {
    if ( time > $expire_timestamp ) {
        ( $token, $expire_time ) = get_access_token;
        $expire_timestamp = time + $expire_time;
    }

    # make requests using $token
}

I am not so familiar with alarm but my attempt(according to docs) will be like below i.e set a SIGALRM handler to call your get_access_token() that will renew your token_expire_timing and then set again the alarm to that token_expire_timing.

my ( $token, $expire_time ) = get_access_token();

eval {
    local $SIG{ALRM} = sub {
        ( $token, $expire_time ) = get_access_token();
    };
    alarm $expire_time;    # schedule alarm in $expire_time
    while (1) {
        # your API requests here with $token
    }
    alarm 0; #cancel the alarm
};
if ($@) {
    die "$@\n"; #propagate some other errors
}

sub get_access_token {

    # some token generation code
    ( $token, $expire_time );
}
Sign up to request clarification or add additional context in comments.

6 Comments

Yes, of course, but I had hoped to avoid hundreds if not thousands of calls to time() for each access token.
I will update the solution with alarm also, give me some time. Choose what suits you best.
Thanks, looking forward to it!
I've added a response to your answer. Not sure how to add a long response w/out doing this, as the comments must be reasonably short.
Microoptimization. time calls are extraordinarily cheap relative to, say, a call to a typical public API
|
0

Okay, so time() and arithmetic it is; cuteness goes out the window. Thanks to all who gave suggestions:

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

my $counter = 0;

my ($token, $expire_time, $expire_ts) = get_access_token();

my $loop = 1;
while ($loop) {
    if (time() > $expire_ts) {
        ($token, $expire_time, $expire_ts) = get_access_token();
    }
    if ($counter == 14) { $loop = 0; }
    say "[ $token ] $counter";
    $counter = $counter + 1;
    sleep(1);
}

exit;


### subroutines

sub get_access_token {
    my $token = localtime(time());
    my $expire_time = 3;
    my $expire_ts = time() + $expire_time;
    # some token generation code
    return ( $token, $expire_time, $expire_ts );
}

output:

$ ./timeTest.pl
[ Fri Feb 26 17:05:46 2016 ] 0
[ Fri Feb 26 17:05:46 2016 ] 1
[ Fri Feb 26 17:05:46 2016 ] 2
[ Fri Feb 26 17:05:46 2016 ] 3
[ Fri Feb 26 17:05:50 2016 ] 4
[ Fri Feb 26 17:05:50 2016 ] 5
[ Fri Feb 26 17:05:50 2016 ] 6
[ Fri Feb 26 17:05:50 2016 ] 7
[ Fri Feb 26 17:05:54 2016 ] 8
[ Fri Feb 26 17:05:54 2016 ] 9
[ Fri Feb 26 17:05:54 2016 ] 10
[ Fri Feb 26 17:05:54 2016 ] 11
[ Fri Feb 26 17:05:58 2016 ] 12
[ Fri Feb 26 17:05:58 2016 ] 13
[ Fri Feb 26 17:05:58 2016 ] 14

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.