All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.apphosting.runtime.jetty.DeferredDatastoreSessionStore Maven / Gradle / Ivy

There is a newer version: 2.0.31
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.apphosting.runtime.jetty;

import static com.google.appengine.api.taskqueue.RetryOptions.Builder.withTaskAgeLimitSeconds;
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withPayload;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.taskqueue.DeferredTask;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TransientFailureException;
import com.google.apphosting.runtime.SessionStore.Retryable;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Optional;
import org.eclipse.jetty.session.SessionData;

/**
 * A {@link DatastoreSessionStore.SessionDataStoreImpl} extension that defers all datastore writes
 * via the taskqueue.
 */
class DeferredDatastoreSessionStore extends DatastoreSessionStore.SessionDataStoreImpl {

  /** Try to save the session state for 10 seconds, then give up. */
  private static final int SAVE_TASK_AGE_LIMIT_SECS = 10;

  // The DeferredTask implementations we use to put and delete session data in
  // the datastore are are general-purpose, but we're not ready to expose them
  // in the public api, so we access them via reflection.
  private static final Constructor putDeferredTaskConstructor;
  private static final Constructor deleteDeferredTaskConstructor;

  static {
    putDeferredTaskConstructor =
        getConstructor(
            DeferredTask.class.getPackage().getName() + ".DatastorePutDeferredTask", Entity.class);
    deleteDeferredTaskConstructor =
        getConstructor(
            DeferredTask.class.getPackage().getName() + ".DatastoreDeleteDeferredTask", Key.class);
  }

  private final Queue queue;

  DeferredDatastoreSessionStore(Optional queueName) {
    this.queue =
        queueName.isPresent()
            ? QueueFactory.getQueue(queueName.get())
            : QueueFactory.getDefaultQueue();
  }

  @Override
  public void doStore(String id, SessionData data, long lastSaveTime)
      throws IOException, Retryable {
    try {
      // Setting a timeout on retries to reduce the likelihood that session
      // state "reverts."  This can happen if a session in state s1 is saved
      // but the write fails.  Then the session in state s2 is saved and the
      // write succeeds.  Then a retry of the save of the session in s1
      // succeeds.  We could use version numbers in the session to detect this
      // scenario, but it doesn't seem worth it.
      // The length of this timeout has been chosen arbitrarily.  Maybe let
      // users set it?
      Entity e = entityFromSession(id, data);

      queue.add(
          withPayload(newDeferredTask(putDeferredTaskConstructor, e))
              .retryOptions(withTaskAgeLimitSeconds(SAVE_TASK_AGE_LIMIT_SECS)));
    } catch (ReflectiveOperationException e) {
      throw new IOException(e);
    } catch (TransientFailureException e) {
      throw new Retryable(e);
    }
  }

  @Override
  public boolean delete(String id) throws IOException {
    try {
      Key key = createKeyForSession(id);
      // We'll let this task retry indefinitely.
      queue.add(withPayload(newDeferredTask(deleteDeferredTaskConstructor, key)));
    } catch (ReflectiveOperationException e) {
      throw new IOException(e);
    }
    return true;
  }

  /**
   * Helper method that returns a 1-arg constructor taking an arg of the given type for the given
   * class name
   */
  private static Constructor getConstructor(String clsName, Class argType) {
    try {
      @SuppressWarnings("unchecked")
      Class cls = (Class) Class.forName(clsName);
      Constructor ctor = cls.getConstructor(argType);
      ctor.setAccessible(true);
      return ctor;
    } catch (ReflectiveOperationException e) {
      throw new ExceptionInInitializerError(e);
    }
  }

  /**
   * Helper method that constructs a {@link DeferredTask} using the given constructor, passing in
   * the given arg as a parameter.
   *
   * 

We used to construct an instance of a DeferredTask implementation that lived in * runtime-shared.jar, but this resulted in much heartache: http://b/5386803. We tried resolving * this in a number of ways, but ultimately the simplest solution was to just create the * DeferredTask implementations we needed in the runtime jar and the api jar. We load them from * the runtime jar here and we load them from the api jar in the servlet that deserializes the * tasks. */ private static DeferredTask newDeferredTask(Constructor ctor, Object arg) throws ReflectiveOperationException { return ctor.newInstance(arg); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy