8

I have this function that tries to read some values from cache. But if value does not exists it should call alternative source API and save new value into the cache. However, server is very overloaded and almost each time when value does not exists more then one requests are created (a lot of API calls) and each of them will store new vale into cache. However, what I want is to be able to call API many times, but only one process/request to be able to store it in cache:

function fetch_cache($key, $alternativeSource) {
    $redis = new Redis();
    $redis->pconnect(ENV_REDIS_HOST);
    $value = $redis->get($key);

    if( $value === NULL ) {
        $value = file_get_contents($alternativeSource);

        // here goes part that I need help with
        $semaphore = sem_get(6000, 1); // does this need to be called each time this function is called?
        if( $semaphore === FALSE ) {
            // This means I have failed to create semaphore?
        }

        if( sem_aquire($semaphore, true) ) {
            // we have aquired semaphore so here
            $redis->set($key, $value);
            sem_release($semaphore); // releasing lock
        }

        // This must be call because I have called sem_get()?
        sem_remove($semaphore);
    }

    return $value;
}

Is this proper use of semaphore in PHP5?

clzola
  • 1,925
  • 3
  • 30
  • 49
  • Would it be more appropriate to use transactions in redis to set the keys? i.e. start a transaction in redis - check if key isn't set - if not set and return it else return it. Similar effect but keeps the PHP simple? – Ryan Vincent Oct 11 '16 at 13:17
  • That is actually good proposal. – clzola Oct 14 '16 at 08:52

2 Answers2

8

Short answer

  1. You don't need to create and remove semaphores within the fetch_cache function. Put sem_get() into an initialization method (such as __construct).
  2. You should remove semaphores with sem_remove(), but in a cleanup method (such as __destruct). Or, you might want to keep them even longer - depends on the logic of your application.
  3. Use sem_acquire() to acquire locks, and sem_release() to release them.

Description

sem_get()

Creates a set of three semaphores.

The underlying C function semget is not atomic. There is a possibility of race condition when two processes trying to call semget. Therefore, semget should be called in some initialization process. The PHP extension overcomes this issue by means of three semaphores:

Semaphore 0 a.k.a. SYSVSEM_SEM

Is initialized to sem_get's $max_acquire and decremented as processes acquires it.

The first process that called sem_get fetches the value of SYSVSEM_USAGEsemaphore (see below). For the first process, it equals to 1, because the extension sets it to 1 with atomic semop function right after semget. And if this is really the first process, the extension assigns SYSVSEM_SEM semaphore value to $max_acquire.

Semaphore 1 a.k.a. SYSVSEM_USAGE

The number of processes using the semaphore.

Semaphore 2 a.k.a. SYSVSEM_SETVAL

Plays a role of a lock for internal SETVAL and GETVAL operations (see man 2 semctl). For example, it is set to 1 while the extension sets SYSVSEM_SEM to $max_acquire, then is reset back to zero.

Finally, sem_get wraps a structure (containing the semaphore set ID, key and other information) into a PHP resource and returns it.

So you should call it in some initialization process, when you're only preparing to work with semaphores.

sem_acquire()

This is where the $max_acquire goes into play.

SYSVSEM_SEM's value (let's call it semval) is initially equal to $max_acquire. semop() blocks until semval becomes greater than or equal to 1. Then 1 is substracted from semval.

If $max_acquire = 1, then semval becomes zero after the first call, and the next calls to sem_acquire() will block until semval is restored by sem_release() call.

Call it when you need to acquire the next "lock" from the available set ($max_acquire).

sem_release()

Does pretty much the same as sem_acquire(), except it increments SYSVSEM_SEM's value.

Call it when you need to no longer need the "lock" acquired previously with sem_acquire().

sem_remove()

Immediately removes the semaphore set, awakening allprocesses blocked in semop on the set (from IPC_RMID section, SEMCTL(2) man page).

So this is effectively the same as removing a semaphore with ipcrm command.

Stefanos Kargas
  • 10,547
  • 22
  • 76
  • 101
Ruslan Osmanov
  • 20,486
  • 7
  • 46
  • 60
0

The file permissions should be 0666 instead of 6000 for what you're trying to do.