001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.reef.runtime.common.driver.evaluator;
020
021import org.apache.reef.annotations.audience.DriverSide;
022import org.apache.reef.annotations.audience.Private;
023import org.apache.reef.tang.ConfigurationProvider;
024import org.apache.reef.driver.context.ActiveContext;
025import org.apache.reef.driver.context.FailedContext;
026import org.apache.reef.driver.evaluator.AllocatedEvaluator;
027import org.apache.reef.driver.evaluator.EvaluatorDescriptor;
028import org.apache.reef.driver.evaluator.EvaluatorType;
029import org.apache.reef.driver.parameters.EvaluatorConfigurationProviders;
030import org.apache.reef.driver.task.FailedTask;
031import org.apache.reef.exception.EvaluatorException;
032import org.apache.reef.exception.EvaluatorKilledByResourceManagerException;
033import org.apache.reef.io.naming.Identifiable;
034import org.apache.reef.proto.EvaluatorRuntimeProtocol;
035import org.apache.reef.proto.ReefServiceProtos;
036import org.apache.reef.runtime.common.DriverRestartCompleted;
037import org.apache.reef.runtime.common.driver.DriverStatusManager;
038import org.apache.reef.runtime.common.driver.api.ResourceLaunchEvent;
039import org.apache.reef.runtime.common.driver.api.ResourceReleaseEventImpl;
040import org.apache.reef.runtime.common.driver.api.ResourceLaunchHandler;
041import org.apache.reef.runtime.common.driver.api.ResourceReleaseHandler;
042import org.apache.reef.runtime.common.driver.context.ContextControlHandler;
043import org.apache.reef.runtime.common.driver.context.ContextRepresenters;
044import org.apache.reef.runtime.common.driver.idle.EventHandlerIdlenessSource;
045import org.apache.reef.runtime.common.driver.resourcemanager.ResourceStatusEvent;
046import org.apache.reef.runtime.common.driver.task.TaskRepresenter;
047import org.apache.reef.runtime.common.utils.ExceptionCodec;
048import org.apache.reef.runtime.common.utils.RemoteManager;
049import org.apache.reef.tang.annotations.Name;
050import org.apache.reef.tang.annotations.NamedParameter;
051import org.apache.reef.tang.annotations.Parameter;
052import org.apache.reef.tang.formats.ConfigurationSerializer;
053import org.apache.reef.util.Optional;
054import org.apache.reef.util.logging.LoggingScopeFactory;
055import org.apache.reef.wake.EventHandler;
056import org.apache.reef.wake.remote.RemoteMessage;
057import org.apache.reef.wake.time.Clock;
058import org.apache.reef.wake.time.event.Alarm;
059
060import javax.inject.Inject;
061import java.io.File;
062import java.util.List;
063import java.util.Set;
064import java.util.logging.Level;
065import java.util.logging.Logger;
066
067/**
068 * Manages a single Evaluator instance including all lifecycle instances:
069 * (AllocatedEvaluator, CompletedEvaluator, FailedEvaluator).
070 * <p/>
071 * A (periodic) heartbeat channel is established EvaluatorRuntime -> EvaluatorManager.
072 * The EvaluatorRuntime will (periodically) send (status) messages to the EvaluatorManager using this
073 * heartbeat channel.
074 * <p/>
075 * A (push-based) EventHandler channel is established EvaluatorManager -> EvaluatorRuntime.
076 * The EvaluatorManager uses this to forward Driver messages, launch Tasks, and initiate
077 * control information (e.g., shutdown, suspend).
078 */
079@Private
080@DriverSide
081public final class EvaluatorManager implements Identifiable, AutoCloseable {
082
083  private final static Logger LOG = Logger.getLogger(EvaluatorManager.class.getName());
084
085  private final EvaluatorHeartBeatSanityChecker sanityChecker = new EvaluatorHeartBeatSanityChecker();
086  private final Clock clock;
087  private final ResourceReleaseHandler resourceReleaseHandler;
088  private final ResourceLaunchHandler resourceLaunchHandler;
089  private final String evaluatorId;
090  private final EvaluatorDescriptorImpl evaluatorDescriptor;
091  private final ContextRepresenters contextRepresenters;
092  private final EvaluatorMessageDispatcher messageDispatcher;
093  private final EvaluatorControlHandler evaluatorControlHandler;
094  private final ContextControlHandler contextControlHandler;
095  private final EvaluatorStatusManager stateManager;
096  private final ExceptionCodec exceptionCodec;
097  private final DriverStatusManager driverStatusManager;
098  private final EventHandlerIdlenessSource idlenessSource;
099  private final LoggingScopeFactory loggingScopeFactory;
100
101
102  // Mutable fields
103  private Optional<TaskRepresenter> task = Optional.empty();
104  private boolean isResourceReleased = false;
105
106  @Inject
107  private EvaluatorManager(
108      final Clock clock,
109      final RemoteManager remoteManager,
110      final ResourceReleaseHandler resourceReleaseHandler,
111      final ResourceLaunchHandler resourceLaunchHandler,
112      final @Parameter(EvaluatorIdentifier.class) String evaluatorId,
113      final @Parameter(EvaluatorDescriptorName.class) EvaluatorDescriptorImpl evaluatorDescriptor,
114      final ContextRepresenters contextRepresenters,
115      final ConfigurationSerializer configurationSerializer,
116      final EvaluatorMessageDispatcher messageDispatcher,
117      final EvaluatorControlHandler evaluatorControlHandler,
118      final ContextControlHandler contextControlHandler,
119      final EvaluatorStatusManager stateManager,
120      final DriverStatusManager driverStatusManager,
121      final ExceptionCodec exceptionCodec,
122      final EventHandlerIdlenessSource idlenessSource,
123      final LoggingScopeFactory loggingScopeFactory,
124      final @Parameter(EvaluatorConfigurationProviders.class) Set<ConfigurationProvider> evaluatorConfigurationProviders) {
125    this.contextRepresenters = contextRepresenters;
126    this.idlenessSource = idlenessSource;
127    LOG.log(Level.FINEST, "Instantiating 'EvaluatorManager' for evaluator: {0}", evaluatorId);
128    this.clock = clock;
129    this.resourceReleaseHandler = resourceReleaseHandler;
130    this.resourceLaunchHandler = resourceLaunchHandler;
131    this.evaluatorId = evaluatorId;
132    this.evaluatorDescriptor = evaluatorDescriptor;
133
134    this.messageDispatcher = messageDispatcher;
135    this.evaluatorControlHandler = evaluatorControlHandler;
136    this.contextControlHandler = contextControlHandler;
137    this.stateManager = stateManager;
138    this.driverStatusManager = driverStatusManager;
139    this.exceptionCodec = exceptionCodec;
140    this.loggingScopeFactory = loggingScopeFactory;
141
142    final AllocatedEvaluator allocatedEvaluator =
143        new AllocatedEvaluatorImpl(this, remoteManager.getMyIdentifier(), configurationSerializer, getJobIdentifier(), loggingScopeFactory, evaluatorConfigurationProviders);
144    LOG.log(Level.FINEST, "Firing AllocatedEvaluator event for Evaluator with ID [{0}]", evaluatorId);
145    this.messageDispatcher.onEvaluatorAllocated(allocatedEvaluator);
146    LOG.log(Level.FINEST, "Instantiated 'EvaluatorManager' for evaluator: [{0}]", this.getId());
147  }
148
149  /**
150   * Get the id of current job/application
151   */
152  public static String getJobIdentifier() {
153    // TODO: currently we obtain the job id directly by parsing execution (container) directory path
154    // #845 is open to get the id from RM properly
155    for (File directory = new File(System.getProperty("user.dir"));
156         directory != null; directory = directory.getParentFile()) {
157      final String currentDirectoryName = directory.getName();
158      if (currentDirectoryName.toLowerCase().contains("application_")) {
159        return currentDirectoryName;
160      }
161    }
162    // cannot find a directory that contains application_, presumably we are on local runtime
163    // again, this is a hack for now, we need #845 as a proper solution
164    return "REEF_LOCAL_RUNTIME";
165  }
166
167  private static boolean isDoneOrFailedOrKilled(final ResourceStatusEvent resourceStatusEvent) {
168    return resourceStatusEvent.getState() == ReefServiceProtos.State.DONE ||
169        resourceStatusEvent.getState() == ReefServiceProtos.State.FAILED ||
170        resourceStatusEvent.getState() == ReefServiceProtos.State.KILLED;
171  }
172
173  @Override
174  public String getId() {
175    return this.evaluatorId;
176  }
177
178  public void setType(final EvaluatorType type) {
179    this.evaluatorDescriptor.setType(type);
180  }
181
182  public EvaluatorDescriptor getEvaluatorDescriptor() {
183    return this.evaluatorDescriptor;
184  }
185
186  @Override
187  public void close() {
188    synchronized (this.evaluatorDescriptor) {
189      if (this.stateManager.isRunning()) {
190        LOG.log(Level.WARNING, "Dirty shutdown of running evaluator id[{0}]", getId());
191        try {
192          // Killing the evaluator means that it doesn't need to send a confirmation; it just dies.
193          final EvaluatorRuntimeProtocol.EvaluatorControlProto evaluatorControlProto =
194              EvaluatorRuntimeProtocol.EvaluatorControlProto.newBuilder()
195                  .setTimestamp(System.currentTimeMillis())
196                  .setIdentifier(getId())
197                  .setKillEvaluator(EvaluatorRuntimeProtocol.KillEvaluatorProto.newBuilder().build())
198                  .build();
199          sendEvaluatorControlMessage(evaluatorControlProto);
200        } finally {
201          this.stateManager.setKilled();
202        }
203      }
204
205
206      if (!this.isResourceReleased) {
207        this.isResourceReleased = true;
208        try {
209        /* We need to wait awhile before returning the container to the RM in order to
210         * give the EvaluatorRuntime (and Launcher) time to cleanly exit. */
211          this.clock.scheduleAlarm(100, new EventHandler<Alarm>() {
212            @Override
213            public void onNext(final Alarm alarm) {
214              EvaluatorManager.this.resourceReleaseHandler.onNext(
215                  ResourceReleaseEventImpl.newBuilder()
216                      .setIdentifier(EvaluatorManager.this.evaluatorId).build()
217              );
218            }
219          });
220        } catch (final IllegalStateException e) {
221          LOG.log(Level.WARNING, "Force resource release because the client closed the clock.", e);
222          EvaluatorManager.this.resourceReleaseHandler.onNext(
223              ResourceReleaseEventImpl.newBuilder()
224                  .setIdentifier(EvaluatorManager.this.evaluatorId).build()
225          );
226        }
227      }
228    }
229    this.idlenessSource.check();
230  }
231
232  /**
233   * Return true if the state is DONE, FAILED, or KILLED,
234   * <em>and</em> there are no messages queued or in processing.
235   */
236  public boolean isClosed() {
237    return this.messageDispatcher.isEmpty() &&
238        (this.stateManager.isDoneOrFailedOrKilled());
239  }
240
241  /**
242   * EvaluatorException will trigger is FailedEvaluator and state transition to FAILED
243   *
244   * @param exception on the EvaluatorRuntime
245   */
246  public void onEvaluatorException(final EvaluatorException exception) {
247    synchronized (this.evaluatorDescriptor) {
248      if (this.stateManager.isDoneOrFailedOrKilled()) {
249        LOG.log(Level.FINE, "Ignoring an exception receivedfor Evaluator {0} which is already in state {1}.",
250            new Object[]{this.getId(), this.stateManager});
251        return;
252      }
253
254      LOG.log(Level.WARNING, "Failed evaluator: " + getId(), exception);
255
256      try {
257
258        final List<FailedContext> failedContextList = this.contextRepresenters.getFailedContextsForEvaluatorFailure();
259
260        final Optional<FailedTask> failedTaskOptional;
261        if (this.task.isPresent()) {
262          final String taskId = this.task.get().getId();
263          final Optional<ActiveContext> evaluatorContext = Optional.empty();
264          final Optional<byte[]> bytes = Optional.empty();
265          final Optional<Throwable> taskException = Optional.<Throwable>of(new Exception("Evaluator crash"));
266          final String message = "Evaluator crash";
267          final Optional<String> description = Optional.empty();
268          final FailedTask failedTask = new FailedTask(taskId, message, description, taskException, bytes, evaluatorContext);
269          failedTaskOptional = Optional.of(failedTask);
270        } else {
271          failedTaskOptional = Optional.empty();
272        }
273
274
275        this.messageDispatcher.onEvaluatorFailed(new FailedEvaluatorImpl(exception, failedContextList, failedTaskOptional, this.evaluatorId));
276
277      } catch (final Exception e) {
278        LOG.log(Level.SEVERE, "Exception while handling FailedEvaluator", e);
279      } finally {
280        this.stateManager.setFailed();
281        close();
282      }
283    }
284  }
285
286  public void onEvaluatorHeartbeatMessage(
287      final RemoteMessage<EvaluatorRuntimeProtocol.EvaluatorHeartbeatProto> evaluatorHeartbeatProtoRemoteMessage) {
288
289    final EvaluatorRuntimeProtocol.EvaluatorHeartbeatProto evaluatorHeartbeatProto =
290        evaluatorHeartbeatProtoRemoteMessage.getMessage();
291    LOG.log(Level.FINEST, "Evaluator heartbeat: {0}", evaluatorHeartbeatProto);
292
293    synchronized (this.evaluatorDescriptor) {
294      if (this.stateManager.isDoneOrFailedOrKilled()) {
295        LOG.log(Level.FINE, "Ignoring an heartbeat received for Evaluator {0} which is already in state {1}.",
296            new Object[]{this.getId(), this.stateManager});
297        return;
298      }
299
300      this.sanityChecker.check(evaluatorId, evaluatorHeartbeatProto.getTimestamp());
301      final String evaluatorRID = evaluatorHeartbeatProtoRemoteMessage.getIdentifier().toString();
302
303      // first message from a running evaluator trying to re-establish communications
304      if (evaluatorHeartbeatProto.getRecovery()) {
305        this.evaluatorControlHandler.setRemoteID(evaluatorRID);
306        this.stateManager.setRunning();
307
308        this.driverStatusManager.oneContainerRecovered();
309        final int numRecoveredContainers = this.driverStatusManager.getNumRecoveredContainers();
310
311        LOG.log(Level.FINE, "Received recovery heartbeat from evaluator {0}.", this.evaluatorId);
312        final int expectedEvaluatorsNumber = this.driverStatusManager.getNumPreviousContainers();
313
314        if (numRecoveredContainers > expectedEvaluatorsNumber) {
315          LOG.log(Level.SEVERE, "expecting only [{0}] recovered evaluators, but [{1}] evaluators have checked in.",
316              new Object[]{expectedEvaluatorsNumber, numRecoveredContainers});
317          throw new RuntimeException("More then expected number of evaluators are checking in during recovery.");
318        } else if (numRecoveredContainers == expectedEvaluatorsNumber) {
319          LOG.log(Level.INFO, "All [{0}] expected evaluators have checked in. Recovery completed.", expectedEvaluatorsNumber);
320          this.driverStatusManager.setRestartCompleted();
321          this.messageDispatcher.OnDriverRestartCompleted(new DriverRestartCompleted(System.currentTimeMillis()));
322        } else {
323          LOG.log(Level.INFO, "expecting [{0}] recovered evaluators, [{1}] evaluators have checked in.",
324              new Object[]{expectedEvaluatorsNumber, numRecoveredContainers});
325        }
326      }
327
328      // If this is the first message from this Evaluator, register it.
329      if (this.stateManager.isSubmitted()) {
330        this.evaluatorControlHandler.setRemoteID(evaluatorRID);
331        this.stateManager.setRunning();
332        LOG.log(Level.FINEST, "Evaluator {0} is running", this.evaluatorId);
333      }
334
335      // Process the Evaluator status message
336      if (evaluatorHeartbeatProto.hasEvaluatorStatus()) {
337        this.onEvaluatorStatusMessage(evaluatorHeartbeatProto.getEvaluatorStatus());
338      }
339
340      // Process the Context status message(s)
341      final boolean informClientOfNewContexts = !evaluatorHeartbeatProto.hasTaskStatus();
342      this.contextRepresenters.onContextStatusMessages(evaluatorHeartbeatProto.getContextStatusList(),
343          informClientOfNewContexts);
344
345      // Process the Task status message
346      if (evaluatorHeartbeatProto.hasTaskStatus()) {
347        this.onTaskStatusMessage(evaluatorHeartbeatProto.getTaskStatus());
348      }
349      LOG.log(Level.FINE, "DONE with evaluator heartbeat from Evaluator {0}", this.getId());
350    }
351  }
352
353  /**
354   * Process a evaluator status message.
355   *
356   * @param message
357   */
358  private synchronized void onEvaluatorStatusMessage(final ReefServiceProtos.EvaluatorStatusProto message) {
359
360    switch (message.getState()) {
361      case DONE:
362        this.onEvaluatorDone(message);
363        break;
364      case FAILED:
365        this.onEvaluatorFailed(message);
366        break;
367      case INIT:
368      case KILLED:
369      case RUNNING:
370      case SUSPEND:
371        break;
372    }
373  }
374
375  /**
376   * Process an evaluator message that indicates that the evaluator shut down cleanly.
377   *
378   * @param message
379   */
380  private synchronized void onEvaluatorDone(final ReefServiceProtos.EvaluatorStatusProto message) {
381    assert (message.getState() == ReefServiceProtos.State.DONE);
382    LOG.log(Level.FINEST, "Evaluator {0} done.", getId());
383    this.stateManager.setDone();
384    this.messageDispatcher.onEvaluatorCompleted(new CompletedEvaluatorImpl(this.evaluatorId));
385    close();
386  }
387
388  /**
389   * Process an evaluator message that indicates a crash.
390   *
391   * @param evaluatorStatusProto
392   */
393  private synchronized void onEvaluatorFailed(final ReefServiceProtos.EvaluatorStatusProto evaluatorStatusProto) {
394    assert (evaluatorStatusProto.getState() == ReefServiceProtos.State.FAILED);
395    final EvaluatorException evaluatorException;
396    if (evaluatorStatusProto.hasError()) {
397      final Optional<Throwable> exception = this.exceptionCodec.fromBytes(evaluatorStatusProto.getError().toByteArray());
398      if (exception.isPresent()) {
399        evaluatorException = new EvaluatorException(getId(), exception.get());
400      } else {
401        evaluatorException = new EvaluatorException(getId(), new Exception("Exception sent, but can't be deserialized"));
402      }
403    } else {
404      evaluatorException = new EvaluatorException(getId(), new Exception("No exception sent"));
405    }
406    onEvaluatorException(evaluatorException);
407  }
408
409  public void onResourceLaunch(final ResourceLaunchEvent resourceLaunchEvent) {
410    synchronized (this.evaluatorDescriptor) {
411      if (this.stateManager.isAllocated()) {
412        this.stateManager.setSubmitted();
413        this.resourceLaunchHandler.onNext(resourceLaunchEvent);
414      } else {
415        throw new RuntimeException("Evaluator manager expected " + EvaluatorState.ALLOCATED +
416            " state but instead is in state " + this.stateManager);
417      }
418    }
419  }
420
421  /**
422   * Packages the ContextControlProto in an EvaluatorControlProto and forward it to the EvaluatorRuntime
423   *
424   * @param contextControlProto message contains context control info.
425   */
426  public void sendContextControlMessage(final EvaluatorRuntimeProtocol.ContextControlProto contextControlProto) {
427    synchronized (this.evaluatorDescriptor) {
428      LOG.log(Level.FINEST, "Context control message to {0}", this.evaluatorId);
429      this.contextControlHandler.send(contextControlProto);
430    }
431  }
432
433  /**
434   * Forward the EvaluatorControlProto to the EvaluatorRuntime
435   *
436   * @param evaluatorControlProto message contains evaluator control information.
437   */
438  void sendEvaluatorControlMessage(final EvaluatorRuntimeProtocol.EvaluatorControlProto evaluatorControlProto) {
439    synchronized (this.evaluatorDescriptor) {
440      this.evaluatorControlHandler.send(evaluatorControlProto);
441    }
442  }
443
444  /**
445   * Handle task status messages.
446   *
447   * @param taskStatusProto message contains the current task status.
448   */
449  private void onTaskStatusMessage(final ReefServiceProtos.TaskStatusProto taskStatusProto) {
450
451    if (!(this.task.isPresent() && this.task.get().getId().equals(taskStatusProto.getTaskId()))) {
452      if (taskStatusProto.getState() == ReefServiceProtos.State.INIT ||
453          taskStatusProto.getState() == ReefServiceProtos.State.FAILED ||
454          taskStatusProto.getRecovery() // for task from recovered evaluators
455          ) {
456
457        // FAILED is a legal first state of a Task as it could have failed during construction.
458        this.task = Optional.of(
459            new TaskRepresenter(taskStatusProto.getTaskId(),
460                this.contextRepresenters.getContext(taskStatusProto.getContextId()),
461                this.messageDispatcher,
462                this,
463                this.exceptionCodec));
464      } else {
465        throw new RuntimeException("Received an message of state " + taskStatusProto.getState() +
466            ", not INIT or FAILED for Task " + taskStatusProto.getTaskId() + " which we haven't heard from before.");
467      }
468    }
469    this.task.get().onTaskStatusMessage(taskStatusProto);
470
471    if (this.task.get().isNotRunning()) {
472      LOG.log(Level.FINEST, "Task no longer running. De-registering it.");
473      this.task = Optional.empty();
474    }
475  }
476
477  /**
478   * Resource status information from the (actual) resource manager.
479   */
480  public void onResourceStatusMessage(final ResourceStatusEvent resourceStatusEvent) {
481    synchronized (this.evaluatorDescriptor) {
482      LOG.log(Level.FINEST, "Resource manager state update: {0}", resourceStatusEvent.getState());
483      if (this.stateManager.isDoneOrFailedOrKilled()) {
484        LOG.log(Level.FINE, "Ignoring resource status update for Evaluator {0} which is already in state {1}.",
485            new Object[]{this.getId(), this.stateManager});
486      } else if (isDoneOrFailedOrKilled(resourceStatusEvent) && this.stateManager.isAllocatedOrSubmittedOrRunning()) {
487        // something is wrong. The resource manager reports that the Evaluator is done or failed, but the Driver assumes
488        // it to be alive.
489        final StringBuilder messageBuilder = new StringBuilder("Evaluator [")
490            .append(this.evaluatorId)
491            .append("] is assumed to be in state [")
492            .append(this.stateManager.toString())
493            .append("]. But the resource manager reports it to be in state [")
494            .append(resourceStatusEvent.getState())
495            .append("].");
496
497        if (this.stateManager.isSubmitted()) {
498          messageBuilder
499              .append(" This most likely means that the Evaluator suffered a failure before establishing a communications link to the driver.");
500        } else if (this.stateManager.isAllocated()) {
501          messageBuilder.append(" This most likely means that the Evaluator suffered a failure before being used.");
502        } else if (this.stateManager.isRunning()) {
503          messageBuilder.append(" This means that the Evaluator failed but wasn't able to send an error message back to the driver.");
504        }
505        if (this.task.isPresent()) {
506          messageBuilder.append(" Task [")
507              .append(this.task.get().getId())
508              .append("] was running when the Evaluator crashed.");
509        }
510        this.isResourceReleased = true;
511
512        if (resourceStatusEvent.getState() == ReefServiceProtos.State.KILLED) {
513          this.onEvaluatorException(new EvaluatorKilledByResourceManagerException(this.evaluatorId, messageBuilder.toString()));
514        } else {
515          this.onEvaluatorException(new EvaluatorException(this.evaluatorId, messageBuilder.toString()));
516        }
517      }
518    }
519  }
520
521  @Override
522  public String toString() {
523    return "EvaluatorManager:"
524        + " id=" + this.evaluatorId
525        + " state=" + this.stateManager
526        + " task=" + this.task;
527  }
528
529  // Dynamic Parameters
530  @NamedParameter(doc = "The Evaluator Identifier.")
531  public final static class EvaluatorIdentifier implements Name<String> {
532  }
533
534  @NamedParameter(doc = "The Evaluator Host.")
535  public final static class EvaluatorDescriptorName implements Name<EvaluatorDescriptorImpl> {
536  }
537}