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

io.gatling.core.action.Action.scala Maven / Gradle / Ivy

There is a newer version: 3.13.1
Show newest version
/*
 * Copyright 2011-2024 GatlingCorp (https://gatling.io)
 *
 * 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
 *
 *  http://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 io.gatling.core.action

import scala.util.control.NonFatal

import io.gatling.commons.util.Clock
import io.gatling.commons.util.Throwables._
import io.gatling.commons.validation._
import io.gatling.core.actor.ActorRef
import io.gatling.core.session.{ Expression, Session }
import io.gatling.core.stats.StatsEngine

import com.typesafe.scalalogging.StrictLogging

/**
 * Top level abstraction in charge of executing concrete actions along a scenario, for example sending an HTTP request.
 */
trait Action extends StrictLogging {
  def name: String

  override def toString: String = name

  @SuppressWarnings(Array("org.wartremover.warts.Recursion"))
  def !(session: Session): Unit = {
    val eventLoop = session.eventLoop
    if (eventLoop.inEventLoop) {
      execute(session)
    } else if (!eventLoop.isShutdown) {
      eventLoop.execute(() => this ! session)
    }
  }

  /**
   * Core method executed when the Action received a Session message
   *
   * @param session
   *   the session of the virtual user
   * @return
   *   Nothing
   */
  protected def execute(session: Session): Unit
}

/**
 * An Action that is to be chained with another. Almost all Gatling Actions are Chainable. For example, the final Action at the end of a scenario workflow is
 * not.
 */
trait ChainableAction extends Action {

  def statsEngine: StatsEngine

  /**
   * @return
   *   the next Action in the scenario workflow
   */
  def next: Action

  override abstract def !(session: Session): Unit =
    try {
      super.!(session)
    } catch {
      case reason: IllegalStateException if reason.getMessage == "cannot enqueue after timer shutdown" =>
        logger.debug(s"'$name' crashed with '${reason.detailedMessage}', ignoring")
      case NonFatal(reason) =>
        if (logger.underlying.isInfoEnabled) {
          logger.error(s"'$name' crashed on session $session, forwarding to the next one", reason)
        } else {
          logger.error(s"'$name' crashed with '${reason.detailedMessage}', forwarding to the next one")
        }
        next ! session.markAsFailed
      case fatal: Throwable =>
        logger.error(s"Gatling crashed: ${fatal.detailedMessage}", fatal)
        sys.exit(1)
    }

  def recover(session: Session)(v: Validation[_]): Unit =
    v.onFailure { message =>
      logger.error(s"'$name' failed to execute: $message")
      next ! session.markAsFailed
    }
}

class ActorDelegatingAction(val name: String, actor: ActorRef[Session]) extends Action {
  def execute(session: Session): Unit = actor ! session
}

/**
 * An Action that can trigger a forced exit and bypass regular workflow.
 */
trait ExitableAction extends ChainableAction {
  def clock: Clock

  override abstract def !(session: Session): Unit =
    BlockExit.mustExit(session) match {
      case Some(blockExit) => blockExit.exitBlock(statsEngine, clock.nowMillis)
      case _               => super.!(session)
    }
}

trait RequestAction extends ExitableAction {
  def requestName: Expression[String]
  def sendRequest(session: Session): Validation[Unit]

  private def logCrash(session: Session, error: String): Unit =
    requestName(session) match {
      case Success(requestNameValue) =>
        logger.error(s"Failed to build request $requestNameValue: $error")
        statsEngine.logRequestCrash(session.scenario, session.groups, requestNameValue, error)
      case Failure(requestNameError) =>
        val errorMessage = s"Failed to build request name: $requestNameError"
        logger.error(errorMessage)
      // [ee]
    }

  override def execute(session: Session): Unit =
    try {
      sendRequest(session) match {
        case Failure(error) =>
          logCrash(session, error)
          next ! session.markAsFailed
        case _ =>
      }
    } catch {
      case NonFatal(e) =>
        logCrash(session, e.detailedMessage)
        // rethrow so we trigger exception handling in "ChainableAction!"
        throw e
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy