unit uDespachador;
{xDEFINE CONLOG}

interface

uses
  xMatDefs, classes, ubuffrw, uTareas, uconstantes_nettopos, SysUtils,
  uMsgsDespachador, Math, uDatosNodo, uLibFuncionesComunes,
  uFichaNodo, uAuxiliares, uDatosConexionNodo,
{$IFDEF WINDOWS}
  ipcthrd, ShellApi, Windows, Forms, Messages,
{$ELSE}
  timeLib, uEmuladorWinIPC, uWinMsgs, uKeyDir,
{$ENDIF}
{$IFDEF NETTOPOS_DLL}
  uimportnettopos,
{$ELSE}
  unettopos,
  unettopostypes,
  uglobsharedmem,
{$ENDIF}
 udespachador_temporizadores;

const
  //TODO cambiar los nodos por servicios en ejecución
  APP_PREGUNTARESTADO = 'OptDisV2';

//  diasPorMSec = 1 / (1000 * 3600 * 24);
//  tCheckearEstadoNodoMSecs = 10000 * diasPorMSec;
  tCheckearEstadoNodoMSecs = 5000;
  timeOutSems = 5000;

  MAX_N_FALLOS_NODOS = 4;

type
  TEstadoEjecucion = (Error_QuedanTareasObligatoriasSinEjecutar,
                      Error_NoSeEjecutaronAlgunasTareasNoObligatorias,
                      Ok);


  TDespachador = class
    public
      idNodoLocal: Cardinal;
      //Las aplicaciones que quieran llevar información sobre el estado de los
      //nodos deben asignar esta lista
      //Lleva los datos de los nodos que no estan baneados permanentemente
      conjuntoDeEjecucion: TConjuntoDeEjecucion{of TDatosNodo};
      tIntervalo: Integer;
      factorDeAumentoEnTOsPorMultiplesNodos: NReal;

      Constructor Create;
      procedure setTemporizador(temporizador: TTemporizador);
      function idsNodos: TDAofNCardinal;
      procedure setDatosNodos(conjuntoDeEjecucion: TConjuntoDeEjecucion);

      //obtiene la lista de topos registrados activos
      procedure getListaTopos();
      function lanzarEjecucion(nodo: TFichaNodo; nomApp: String): Cardinal;
      //envia a los topos una petición de lanzar el servicio con el ejecutable
      //indicado
      //habría que hacer automatica la detección del nombre del servicio
      procedure lanzarEjecuciones(nomApp: String);
      //envia a los topos una petición de cerrar la aplicación con el nombre
      //indicado
      procedure finalizarEjecucion(nodo: TFichaNodo; nomAplicacion: String);
      procedure finalizarEjecuciones(nomAplicacion: String);

      function forkAndJoin(tarea: TFichaTarea): Integer; overload;
      //Pide un conjunto de tareas a ser ejecutados por los nodos. Si las tareas
      //no tienen forzarNodo ejecutor son ejecutadas por cualquier nodo disponible,
      //sino las ejecuta el nodo especificado. El proceso llamante queda bloqueado
      //hasta que se reciban todas las respuestas o hayan transcurrido timeOutGlobalMSecs
      //milisegundos.
      //Para ese momento los nodos que no hayan respondido se marcan como baneados
      //y no estan disponibles hasta que se chequee su estado. Los nodos que si
      //hayan respondido quedan disponibles para procesar mas tareas
      //retorna 1 si se pudo ejecutar todas las tareas y 0 en caso contrario
      function forkAndJoin(lista: TList; timeOutGlobalMSecs: Cardinal): Integer; overload;

      //Igual que el fork and join, pero cada tIntervalo Msecs llama un application.ProcessMessages
      //por si la aplicación se ejecuta en un único hilo de ejecución
      //function forkAndJoinMonoHilo(lista: TList; timeOutGlobalMSecs: Cardinal): Integer;

      procedure checkearEstadoNodos(nomAplic: String);
      //Maneja la lógica de comunicación del envío de una respuesta. Obtiene
      //el comunicado que se envío y los datos que pueda tener. Debe llamarse
      //en la form de la aplicación en respuesta a un msg de codigo
      //MSGP_RECIBIR_RESPUESTA
      procedure recibirRespuesta(var msj: TMessage);
      procedure Free;

      //No debe llamarse durante la ejecución de un forkAndJoin
      procedure banearNodoPermanentemente(idNodo: Cardinal);
    private
      huboActualizacionTiempos: boolean; //Bandera que indica si se debe actualizar las velocidades de los nodos
      temporizador: TTemporizador;

      flg_evento: boolean;
      multiHilo: boolean;
      nom_smf_Procesando, nom_EventoRespuesta: String;

      //true <=> el topo no estaba corriendo antes de ejecutar el despachador
      ejecuteElTopo: boolean;

      //Cola de TFichaTarea. En el principio estan las tareas que tienen
      //forzarNodoEjecutor = true, al final las que no
      colaTareasSinEnviar: TList;
      //Lista de TFichaTareaEnviada ordenada por momentoTimeOut
      colaTareasEnviadas: TList;

      //Solo se usan en despacharTareasSinEnviar. Si falla la comunicación de una
      //tarea a un nodo encolo a ambos en los fallos de comunicación para sacarlos
      //del actual ciclo de despacho
      colaFallosDeComunicacion: TList;
      colaNodosFallosDeComunicacion: TList;

      //El tiempo en msecs hasta el vencimiento de la proxima tarea
      nextTimeOut: Integer;
      iniForkAndJoin, dtVencimientoGlobal: Int64;

      //Todos los topos encontrados al crear el despachador
      listaTopos: TList{of TFichaNodo};
      //Un nodo esta (D) disponible si esta esperando tarea
      //Un nodo esta (O) ocupado si esta procesando una tarea
      //Un nodo esta (B) baneado si no respondió a una tarea antes que se
      //venciera el timeOutGlobal la lista de tareas de la tarea a la que no
      //respondió
      //Un nodo esta (PB) permanentemente baneado si la capa de aplicación pidió
      //explicitamente que se lo excluyera o si esta baneado y no responde mas
      //de MAX_N_FALLOS_NODOS veces consecutivas
      //Los nodos cambian de una lista a otra según las siguientes transiciónes:
      //D-->O  al enviarTarea correctamente
      //O-->D  llega la respuesta a una tarea antes de que venza el timeOutGlobal
      //       de la lista en la que se pidió
      //O-->B  vence el timeOut de la lista de tareas en que se le pidió una tarea
      //B-->D  se le pregunta el estado al nodo y retorna disponible
      //B-->PB se le pregunta el estado al nodo y no responde MAX_N_FALLOS_NODOS
      //       veces consecutivas o la aplicación llama a banearNodoPermanentemente
      //D-->PB la aplicación llama a banearNodoPermanentemente
      //En el forkAndJoin se recorre la lista de nodos baneados y si paso mas de
      //tiempoParaCheckearEtadoNodoMSecs * nFallosConsecutivos del nodo desde su
      //ultima respuesta se le pregunta el estado
      //Si responde disponible se lo desbanea, si no responde se aumenta en 1
      //el contador de fallos consecutivos. Si el contador es igual a
      //MAX_N_FALLOS_NODOS se lo banea permanentemente

      //Lista de TFichaNodo con los nodos que no estan realizando calculos
      //Estan ordenados por velocidad
      nodosDisponibles: Tlist;
      //Lista de TFichaNodo con los nodos que estan realizando calculos
      //Podría sustituirse por un hash
      nodosOcupados: TList;
      //Lista de TFichaNodo con los nodos que respondieron luego de que vencio el
      //timeOutGlobal de sus tareas asignadas. Vuelven a nodosDisponibles al
      //finalizar el próximo forkAndJoin
      nodosBaneados: Tlist;

      //Nodos que no se consideran en la ejecución ni en la planificación
      nodosBaneadosPermanentemente: Tlist;

      //Variables para medir tiempos
      frecuencia: NReal;    //frecuencia del contador en tics por milisegundo si
                            //se usan timers de alta resolucion, en dias por
                            //milisegundo si no
      invFrecuencia: NReal; //inverso de la frecuencia del contador en milisegundos
                            //por tick si se usan timers de alta resolucion, en
                            //milisegundos por dia sino

      //obtiene el identificador del topo local, si no hay un topo en ejecución
      //intenta ejecutarlo
      procedure getTopoLocal();

      function getNodo(nombreNodo: String): TFichaNodo; overload;
      function getNodo(idNodo: Cardinal): TFichaNodo; overload;
      function getNodoDisponible(idNodo: Cardinal): TFichaNodo;
//      function getFichaTarea(idTarea: Cardinal): TFichaTarea;
      function getIFichaTareaEnviada(idTarea: Cardinal): Integer;

      //Envia a todos los nodos que se encuentren en nodosDisponibles las tareas
      //primeras tareas que encuentre en colaTareasSinEnviar. Envia tantas tareas
      //como nodos disponibles haya, las saca de tareasSinEnviar y las agrega
      //a tareasPedidas ordenadas por timeOut
      procedure despacharTareasSinEnviar;
      function enviarPedidoEjecucion(tarea: TFichaTarea; idNodo, idAplic: Cardinal): Cardinal;

      //Asigna los resultados a la tarea identificada por idTarea en el comunicado
      //Es llamada en recibirRespuesta
      procedure procesarRespuesta(comunicado: TFichaComunicado; resultados: TBuffReader);

      //Si la tarea tiene forzarNodoEjecutor en true le reenvía el comunicado al
      //nodo
      //Si no pregunta el estado del nodo. Si esta esperando mensajes quiere decir
      //que no le llego mi msj, se lo reenvio. Si no quiere decir que dejo de
      //responder. Asigno la tarea al primer nodo disponible. Si no hay vuelvo a
      //encolarla
      procedure reasignarTareasVencidas;

      function preguntarEstadoNodo(idNodo: Cardinal; nomAplic: String): Cardinal; overload;
      function preguntarEstadoNodo(idNodo, idAplic: Cardinal): Cardinal; overload;

      //Checkea el estado de los nodos baneados y si responden los pasa a disponibles
      //Banea los nodos que hayan quedado ocupados y revisa el estado de las tareas
      //pedidas.
      //Si todas se completaron retorna 1, si quedaron algunas tareas no obligatorias
      //sin ejecutar retorna 2 y si quedo alguna tarea obligatoria sin ejecutar
      //retorna 3
      function finalizarForkAndJoin: Integer;
      procedure actualizarDatosNodos;

      //Para debug
      //Escribe el estado del despachador en un momento dado a un archivo de texto
      procedure dumpEstadoDespachador;
  end;

function agregarEnOrdenDeTimeOut(lista: TList; tareaEnviada: TFichaTarea): Integer;
function agregarEnOrdenDeVelocidad(lista: TList; nodo: TFichaNodo): Integer;
procedure agregarTareaSinEnviar(lista: TList; tarea: TFichaTarea);
function sortFichaNodoByVelocidad(Item1, Item2: Pointer): Integer;

implementation

//=======================
//Métodos de TDespachador
//-----------------------

Constructor TDespachador.Create;
var
  pid: Cardinal;
  frecuencia64: int64;
begin
  inherited Create;
  pid:= GetCurrentThreadId;
{$IFDEF WINDOWS}
  nom_smf_Procesando:= 'smf_Procesando' + IntToStr(pid);
  nom_EventoRespuesta:= 'eventoRespuesta' + IntToStr(pid);
{$ELSE}
  nom_smf_Procesando:= keyBaseDir + DirectorySeparator + 'smf_Procesando' + IntToStr(pid);
  nom_EventoRespuesta:= keyBaseDir + DirectorySeparator + 'eventoRespuesta' + IntToStr(pid);

  uKeyDir.CrearArchiKeyString(nom_smf_Procesando);
  uKeyDir.CrearArchiKeyString(nom_EventoRespuesta);
{$ENDIF}

  nodosDisponibles:= TList.Create;
  nodosOcupados:= TList.Create;
  nodosBaneados:= TList.Create;
  nodosBaneadosPermanentemente:= TList.Create;
  colaTareasSinEnviar:= TList.Create;
  colaTareasEnviadas:= TList.Create;
  colaFallosDeComunicacion:= TList.Create;
  colaNodosFallosDeComunicacion:= TList.Create;

  listaTopos:= TList.Create;
  getListaTopos;

{$IFNDEF WINDOWS}
  timeLib.QueryPerformanceFrequency(frecuencia64);
{$ELSE}
  Windows.QueryPerformanceFrequency(frecuencia64);
{$ENDIF}
  frecuencia:= frecuencia64 / 1000;
  invFrecuencia:= 1 / frecuencia;

  tIntervalo:= 25;
end;

procedure TDespachador.setTemporizador(temporizador: TTemporizador);
begin
  huboActualizacionTiempos:= False;
  self.temporizador:= temporizador;
end;

function TDespachador.idsNodos: TDAofNCardinal;
var
  i: Integer;
  res: TDAofNCardinal;
begin
  SetLength(res, listaTopos.Count);
  for i:= 0 to listaTopos.Count - 1 do
    res[i]:= TFichaNodo(listaTopos[i]).idNodo;
  result:= res;
end;

procedure TDespachador.setDatosNodos(conjuntoDeEjecucion: TConjuntoDeEjecucion);
var
  iNodo: Integer;
  fichaNodo: TFichaNodo;
  datosNodo: TDatosNodo;
begin
  if listaTopos.Count <> conjuntoDeEjecucion.Count then
    raise Exception.Create('TDespachador.setDatosNodos, el tamaño de la lista de datos y de la lista de nodos no coincide.');
  for iNodo:= 0 to conjuntoDeEjecucion.Count - 1 do
  begin
    datosNodo:= conjuntoDeEjecucion[iNodo];
    fichaNodo:= getNodo(datosNodo.idNodo);
    if fichaNodo <> NIL then
      datosNodo.nombre:= fichaNodo.nombre
    else
      raise Exception.Create('TDespachador.setDatosNodos, hay un nodo en la lista de datos que no esta en la lista de nodos disponibles.');
  end;
  self.conjuntoDeEjecucion:= conjuntoDeEjecucion;
  actualizarDatosNodos;
end;

function TDespachador.lanzarEjecucion(nodo: TFichaNodo; nomApp: String): Cardinal;
var
  comunicado: TFichaComunicado;
  datosSalientes: TBuffWriter;
  resultado: TBuffReader;
  idPet: Cardinal;
  res: Integer;
  msjError: String;
  nomServicioAsPChar: PAnsiChar;
begin
  comunicado.idNodoOrigen:= idNodoLocal;
  comunicado.idOrigen:= idAplicYo;
  comunicado.idNodoDestino:= nodo.idNodo;
  comunicado.idDestino:= nodo.idTopo;
  comunicado.codigoMsg:= uconstantes_nettopos.MSGP_RUNCMD;
  comunicado.nBytesDatos:= xSizeOf(nomApp);
  datosSalientes:= TBuffWriter.Create(comunicado.nBytesDatos);
  datosSalientes.xString(nomApp);
  idPet:= comunicarTS(@comunicado, datosSalientes.pBuff, 20000);
  if idPet > 0 then
  begin
    if leerFichaComunicado(idPet, @comunicado) > 0 then
    begin
      resultado:= TBuffReader.Create(comunicado.pdatos, comunicado.nBytesDatos);
      resultado.xInteger(res);
      if levantarDatosComunicado(idPet, nil, 0) > 0 then
      begin
        if res = 0 then
        begin
          resultado.Free;
          logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_LEVANTAR_LA_APLICACION) +
                  ' idNodoDestino= ' + nodo.nombre +
                  ' idTopo= ' + IntToStr(nodo.idTopo) +
                  ' Aplic= ' + nomApp);
          result:= 0;
        end
        else if res = -1 then
        begin
          resultado.xString(msjError);
          resultado.Free;
          logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_LEVANTAR_LA_APLICACION) +
                  ' idNodoDestino= ' + nodo.nombre +
                  ' idTopo= ' + IntToStr(nodo.idTopo) +
                  ' Aplic= ' + nomApp +
                  ' MsjError= ' + msjError);
          result:= 0;
        end
        else
        begin
          resultado.Free;
          sleep(1500); //Damos tiempo a que las apps se registren en la dll
          GetMem(nomServicioAsPChar, Length(nomApp) + 1);
          StrPCopy(nomServicioAsPChar, nomApp);
          res:= nodo.idServicio(nomApp);
          FreeMem(nomServicioAsPChar, Length(nomApp) + 1);
          if res <> 0 then
          begin
            nodo.listaServicios.Add(TServicioEnEjecucion.Create(nomApp, res));
            result:= res;
          end
          else
            raise Exception.Create(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_LEVANTAR_LA_APLICACION) +
                                    ' idNodoDestino= ' + nodo.nombre +
                                    ' idTopo= ' + IntToStr(nodo.idTopo) +
                                    ' Aplic= ' + nomApp);
        end;
      end
      else
      begin
        logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_LOS_DATOS_DEL_COMUNICADO_DESDE_LA_DLL));
        result:= 0;
      end;
    end
    else
    begin
      logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_EL_COMUNICADO_DESDE_LA_DLL));
      result:= 0;
    end;
  end
  else
  begin
    logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_LEVANTAR_LA_APLICACION) +
                                              ' idNodoDestino= ' + nodo.nombre +
                                              ' idTopo= ' + IntToStr(nodo.idTopo) +
                                              ' Aplic= ' + nomApp);
    result:= 0;                                              
  end;
  datosSalientes.Free;
end;

procedure TDespachador.lanzarEjecuciones(nomApp: String);
var
  comunicadoSaliente, comunicadoEntrante: TFichaComunicado;
  nodo: TFichaNodo;
  datosSalientes: TBuffWriter;
  resultado: TBuffReader;
  msjError: String;
  idPet: Cardinal;
  i, res: Integer;
begin
  writeln('Lanzar Ejecuciones: ' + nomApp);
  comunicadoSaliente.idNodoOrigen:= idNodoLocal;
  comunicadoSaliente.idOrigen:= idAplicYo;
  comunicadoSaliente.codigoMsg:= uconstantes_nettopos.MSGP_RUNCMD;
  comunicadoSaliente.nBytesDatos:= xSizeOf(nomApp);
  datosSalientes:= TBuffWriter.Create(comunicadoSaliente.nBytesDatos);
  datosSalientes.xString(nomApp);
  for i:= 0 to listaTopos.Count - 1 do
  begin
    nodo:= listaTopos[i];
    if nodo.idServicio(nomApp) = 0 then
    begin
      comunicadoSaliente.idNodoDestino:= nodo.idNodo;
      comunicadoSaliente.idDestino:= nodo.idTopo;
      idPet:= comunicarTS(@comunicadoSaliente, datosSalientes.pBuff, 20000);
      if idPet > 0 then
      begin
        if leerFichaComunicado(idPet, @comunicadoEntrante) > 0 then
        begin
          resultado:= TBuffReader.Create(comunicadoEntrante.pdatos, comunicadoEntrante.nBytesDatos);
          resultado.xInteger(res);
          if levantarDatosComunicado(idPet, nil, 0) > 0 then
          begin
            if res = 0 then
            begin
              logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_LEVANTAR_LA_APLICACION) +
                      ' idNodoDestino= ' + nodo.nombre +
                      ' idTopo= ' + IntToStr(nodo.idTopo) +
                      ' Aplic= ' + nomApp);
            end
            else if res = -1 then
            begin
              resultado.xString(msjError);
              logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_LEVANTAR_LA_APLICACION) +
                      ' idNodoDestino= ' + nodo.nombre +
                      ' idTopo= ' + IntToStr(nodo.idTopo) +
                      ' Aplic= ' + nomApp +
                      ' MsjError= ' + msjError);            
            end;
            resultado.Free;
          end
          else
          begin
            logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_LOS_DATOS_DEL_COMUNICADO_DESDE_LA_DLL));
          end;
        end
        else
        begin
          logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_EL_COMUNICADO_DESDE_LA_DLL));
        end;
      end
      else
      begin
        logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_LEVANTAR_LA_APLICACION) +
                                                  ' idNodoDestino= ' + nodo.nombre +
                                                  ' idTopo= ' + IntToStr(nodo.idTopo) +
                                                  ' Aplic= ' + nomApp);
      end;
    end;
  end;
  datosSalientes.Free;
  sleep(1500);
  for i:= 0 to listaTopos.Count - 1 do
  begin
    nodo:= listaTopos[i];
    if nodo.idServicio(nomApp) = 0 then
      raise Exception.Create('No se pudo ejecutar la aplicacion:' +
                             ' idNodoDestino= ' + nodo.nombre +
                             ' idTopo= ' + IntToStr(nodo.idTopo) +
                             ' Aplic= ' + nomApp);
  end;
end;

procedure TDespachador.finalizarEjecucion(nodo: TFichaNodo; nomAplicacion: String);
var
  comunicado: TFichaComunicado;
  idPeticion: Cardinal;
  datosSalientes: TBuffWriter;

  resultado: TBuffReader;
  res: Integer;
begin
  with comunicado do
  begin
    idNodoOrigen:= idNodoLocal;
    idOrigen:= idAplicYo;
    idNodoDestino:= nodo.idNodo;
    idDestino:= nodo.idTopo;
    codigoMsg:= uconstantes_nettopos.MSGP_CLOSEAPP;
    nBytesDatos:= xSizeOf(nomAplicacion);
  end;
  datosSalientes:= TBuffWriter.Create(comunicado.nBytesDatos);
  datosSalientes.xString(nomAplicacion);
  idPeticion:= comunicarTS(@comunicado, datosSalientes.pBuff, 10000);
  if idPeticion > 0 then
  begin
    if leerFichaComunicado(idPeticion, @comunicado) > 0 then
    begin
      resultado:= TBuffReader.Create(comunicado.pdatos, comunicado.nBytesDatos);
      resultado.xInteger(res);
      resultado.Free;
      if levantarDatosComunicado(idPeticion, nil, 0) > 0 then
      begin
        if res = -1 then
        begin
          logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TERMINAR_LA_APLICACION) +
                                                     ' idNodoDestino= ' + IntToStr(nodo.idNodo) +
                                                     ' idTopo= ' + IntToStr(nodo.idTopo) +
                                                     ' Aplic= ' + nomAplicacion);
        end
        else
        begin
          nodo.quitarServicio(nomAplicacion);
        end;
      end
      else
      begin
        logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_LOS_DATOS_DEL_COMUNICADO_DESDE_LA_DLL));
      end;
    end
    else
      logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_EL_COMUNICADO_DESDE_LA_DLL));
  end
  else
    logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TERMINAR_LA_APLICACION) +
                                               ' idNodoDestino= ' + IntToStr(nodo.idNodo) +
                                               ' idTopo= ' + IntToStr(nodo.idTopo) +
                                               ' Aplic= ' + nomAplicacion);
  datosSalientes.Free;
end;

procedure TDespachador.finalizarEjecuciones(nomAplicacion: String);
var
  i: Integer;
begin
  for i:= 0 to listaTopos.Count - 1 do
    finalizarEjecucion(listaTopos[i], nomAplicacion);
end;

function TDespachador.forkAndJoin(tarea: TFichaTarea): Integer;
var
  listaTareas: TList;
  res: Integer;
begin
  listaTareas:= TList.Create;
  listaTareas.Add(tarea);
  res:= forkAndJoin(listaTareas, tarea.timeOutMSecs);
  listaTareas.Free;
  result:= res;
end;

//Pide un conjunto de tareas a ser ejecutados por los nodos. No importa
//que nodo ejecuta que. El proceso llamante queda bloqueado hasta que se
//reciban todas las respuestas o hayan transcurrido timeOutGlobalMSecs
//milisegundos.
//retorna 1 si se pudo ejecutar todas las tareas y 0 en caso contrario
function TDespachador.forkAndJoin(lista: TList; timeOutGlobalMSecs: Cardinal): Integer;
var
  i, res: Integer;
  smf_Procesando: TMutex;
{$IFDEF WINDOWS}
  eventoRespuesta: TEvent;
{$ELSE}
  eventoRespuesta: TEventRX;
{$ENDIF}
  ahora: Int64;
begin
  multiHilo:= true;

{$IFDEF WINDOWS}
  smf_Procesando:= TMutex.Create(nom_smf_Procesando);
  eventoRespuesta:= TEvent.Create(nom_EventoRespuesta, false);
{$ELSE}
  smf_Procesando:= TMutex.Create(nom_smf_Procesando, 1);
  eventoRespuesta:= TEventRX.Create(nom_EventoRespuesta, 1);
{$ENDIF}
  if not smf_Procesando.Get(timeOutSems) then
    raise Exception.Create('TDespachador.forkAndJoin: Error obteniendo semaforo 1');

{$IFNDEF WINDOWS}
  timeLib.QueryPerformanceCounter(iniForkAndJoin);
{$ELSE}
  Windows.QueryPerformanceCounter(iniForkAndJoin);
{$ENDIF}
  dtVencimientoGlobal:= iniForkAndJoin + trunc(timeOutGlobalMSecs * frecuencia);

  for i:= 0 to lista.Count - 1 do
    agregarTareaSinEnviar(colaTareasSinEnviar, lista[i]);
  despacharTareasSinEnviar;

{$IFNDEF WINDOWS}
  timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
  Windows.QueryPerformanceCounter(ahora);
{$ENDIF}

  while ((colaTareasEnviadas.Count + colaTareasSinEnviar.Count) > 0) and (ahora < dtVencimientoGlobal) do
  begin
    if colaTareasEnviadas.Count = 0 then
    begin
      //si estoy aca hay una tarea con nodo ejecutor forzado a la que se le venció
      //el timeout y cuyo nodo esta ocupado
      //espero un tiempo arbitrario a ver si el nodo responde y en el próximo
      //reasignarTareasVencidas se le asignara la tarea
      sleep(TFichaTarea(colaTareasSinEnviar[0]).timeOutMSecs);
      nextTimeOut:= 0;
      eventoRespuesta:= NIL;
    end
    else
    begin
      nextTimeOut:= round((TFichaTarea(colaTareasEnviadas[0]).dtVencimiento - ahora) * invFrecuencia);
    end;

    smf_Procesando.Release;
    //Si no me despiertan
    if (nextTimeOut <= 0) or not eventoRespuesta.Wait(nextTimeOut) then
    begin
      if not smf_Procesando.Get(timeOutSems) then
        raise Exception.Create('TDespachador.forkAndJoin: Error obteniendo semaforo 2');
{$IFNDEF WINDOWS}
      timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
      Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
      if (ahora < dtVencimientoGlobal) then
        reasignarTareasVencidas;
    end
    else
    begin
      if not smf_Procesando.Get(timeOutSems) then
        raise Exception.Create('TDespachador.forkAndJoin: Error obteniendo semaforo 3');
    end;
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
  end;

  eventoRespuesta.Free;

  res:= finalizarForkAndJoin;
  smf_Procesando.Release;
  smf_Procesando.Free;
  if huboActualizacionTiempos then
  begin
    temporizador.actualizarVelocidades;
    huboActualizacionTiempos:= false;
  end;
  if res = 3 then
    raise Exception.Create('No se pudo ejecutar todas las tareas obligatorias');
  result:= res;
end;

(*function TDespachador.forkAndJoinMonoHilo(lista: TList; timeOutGlobalMSecs: Cardinal): Integer;
var
  i, res: Integer;
  ahora: Int64;
begin
  multiHilo:= false;

{$IFNDEF WINDOWS}
  timeLib.QueryPerformanceCounter(iniForkAndJoin);
{$ELSE}
  Windows.QueryPerformanceCounter(iniForkAndJoin);
{$ENDIF}
  dtVencimientoGlobal:= iniForkAndJoin + trunc(timeOutGlobalMSecs * frecuencia);

  for i:= 0 to lista.Count - 1 do
    agregarTareaSinEnviar(colaTareasSinEnviar, lista[i]);
  despacharTareasSinEnviar;

{$IFNDEF WINDOWS}
  timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
  Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
  while ((colaTareasEnviadas.Count + colaTareasSinEnviar.Count) > 0) and (ahora < dtVencimientoGlobal) do
  begin
    if colaTareasEnviadas.Count = 0 then
    begin
      //si estoy aca hay una tarea con nodo ejecutor forzado a la que se le venció
      //el timeout y cuyo nodo esta ocupado
      //espero un tiempo arbitrario a ver si el nodo responde y en el próximo
      //reasignarTareasVencidas se le asignara la tarea
      sleep(TFichaTarea(colaTareasSinEnviar[0]).timeOutMSecs);
      nextTimeOut:= 0;
    end
    else
      nextTimeOut:= round((TFichaTarea(colaTareasEnviadas[0]).dtVencimiento - ahora) * invFrecuencia);

    if (nextTimeOut > tIntervalo) then
    begin
      nextTimeOut:= nextTimeOut div 2;
      sleep(nextTimeOut);
      Application.ProcessMessages;
    end;

    while (nextTimeOut > tIntervalo) and (not flg_evento) do
    begin
      Sleep(tIntervalo);
      Application.ProcessMessages;
      nextTimeOut:= nextTimeOut - tIntervalo;
    end;
    if (not flg_evento) and (nextTimeOut > 0) then
    begin
      Sleep(nextTimeOut);
      Application.ProcessMessages;
    end;

    if not flg_evento then
    begin
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
      if (ahora < dtVencimientoGlobal) then
        reasignarTareasVencidas;
    end
    else
      flg_evento:= false;
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
  end;

  res:= finalizarForkAndJoin;
  if res = 3 then
    raise Exception.Create('No se pudo ejecutar todas las tareas obligatorias');
  result:= res;
end;*)

function TDespachador.finalizarForkAndJoin: Integer;
var
  estadoEjec: TEstadoEjecucion;
  nodo: TFichaNodo;
  ahora: Int64;
  i: Integer;
begin
  if nodosBaneados.Count > 0 then
  begin
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
    for i:= 0 to nodosBaneados.Count - 1 do
    begin
      nodo:= TFichaNodo(nodosBaneados[i]);
      if (ahora - nodo.dtUltimoPedido) >=
          nodo.nVecesConsecutivasQueNoRespondio * tCheckearEstadoNodoMSecs * frecuencia then
      begin
{$IFNDEF WINDOWS}
        timeLib.QueryPerformanceCounter(nodo.dtUltimoPedido);
{$ELSE}
        Windows.QueryPerformanceCounter(nodo.dtUltimoPedido);
{$ENDIF}
        //TODO cambiar los nodos por servicios en ejecución
        if preguntarEstadoNodo(nodo.idNodo, APP_PREGUNTARESTADO) = 0 then
        begin
          nodo.nVecesConsecutivasQueNoRespondio:= 0;
          writeln('El nodo ', nodo.nombre, ' volvio a responder. ', FormatDateTime(formatofechasConMsecs, now));
          agregarEnOrdenDeVelocidad(nodosDisponibles, nodo);
          nodosBaneados[i]:= NIL;
        end
        else
        begin
          nodo.nVecesConsecutivasQueNoRespondio:= nodo.nVecesConsecutivasQueNoRespondio + 1;
          if nodo.nVecesConsecutivasQueNoRespondio = MAX_N_FALLOS_NODOS then
          begin
            banearNodoPermanentemente(nodo.idNodo);
            writeln('Nodo ', nodo.nombre, ' baneado permanentemente. NFallosConsecutivos: ', nodo.nVecesConsecutivasQueNoRespondio, '. ', FormatDateTime(formatofechasConMsecs, now));
          end
          else
            writeln('Nodo ', nodo.nombre, ' baneado. NFallosConsecutivos: ', nodo.nVecesConsecutivasQueNoRespondio, '. ', FormatDateTime(formatofechasConMsecs, now));
        end;
      end;
    end;
    nodosBaneados.Pack;
  end;
  for i:= 0 to nodosOcupados.Count - 1 do
  begin
    nodo:= TFichaNodo(nodosOcupados[i]);
    nodo.nVecesConsecutivasQueNoRespondio:= 1;
    nodosBaneados.Add(nodo);
    writeln('Nodo ', nodo.nombre, ' baneado. ', FormatDateTime(formatofechasConMsecs, now));
    logError('Nodo ' + nodo.nombre + ' baneado');
  end;
  nodosOcupados.Count:= 0;
  if conjuntoDeEjecucion <> NIL then
    actualizarDatosNodos;

  if (colaTareasSinEnviar.Count + colaTareasEnviadas.Count = 0) then
  begin
    Result:= 1
  end
  else
  begin
    dumpEstadoDespachador;
    estadoEjec:= Error_NoSeEjecutaronAlgunasTareasNoObligatorias;
    for i:= 0 to colaTareasSinEnviar.Count - 1 do
    begin
      TFichaTarea(colaTareasSinEnviar[i]).estado:= Fallada;
      if TFichaTarea(colaTareasSinEnviar[i]).debeEjecutarse then
        estadoEjec:= Error_QuedanTareasObligatoriasSinEjecutar;
      colaTareasSinEnviar[i]:= NIL;
    end;
    colaTareasSinEnviar.Count:= 0;

    for i:= 0 to colaTareasEnviadas.Count - 1 do
    begin
      TFichaTarea(colaTareasEnviadas[i]).estado:= Fallada;
      if TFichaTarea(colaTareasEnviadas[i]).debeEjecutarse then
        estadoEjec:= Error_QuedanTareasObligatoriasSinEjecutar;
      colaTareasEnviadas[i]:= NIL;
    end;
    colaTareasEnviadas.Count:= 0;

    if estadoEjec = Error_NoSeEjecutaronAlgunasTareasNoObligatorias then
      result:= 2
    else 
      result:= 3;
  end;
end;

procedure TDespachador.checkearEstadoNodos(nomAplic: String);
var
  i: Integer;
  nodo: TFichaNodo;
  smf_Procesando: TMutex;
begin
{$IFDEF WINDOWS}
  smf_Procesando:= TMutex.Create(nom_smf_Procesando);
{$ELSE}
  smf_Procesando:= TMutex.Create(nom_smf_Procesando, 1);
{$ENDIF}
  if not smf_Procesando.Get(timeOutSems) then
  begin
    raise Exception.Create('TDespachador.checkearEstadoNodos: error obteniendo semáforo');
  end;
  for i:= 0 to nodosDisponibles.Count - 1 do
  begin
    nodo:= nodosDisponibles[i];
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(nodo.dtUltimoPedido);
{$ELSE}
    Windows.QueryPerformanceCounter(nodo.dtUltimoPedido);
{$ENDIF}
    if preguntarEstadoNodo(nodo.idNodo, nomAplic) <> 0 then
    begin
      nodosOcupados.Add(nodo);
      nodosDisponibles[i]:= NIL;
    end;
  end;
  nodosDisponibles.Pack;

  for i:= 0 to nodosBaneados.Count - 1 do
  begin
    nodo:= nodosBaneados[i];
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(nodo.dtUltimoPedido);
{$ELSE}
    Windows.QueryPerformanceCounter(nodo.dtUltimoPedido);
{$ENDIF}
    if preguntarEstadoNodo(nodo.idNodo, nomAplic) = 0 then
    begin
      nodo.nVecesConsecutivasQueNoRespondio:= 0;
      writeln('El nodo ', TFichaNodo(nodosBaneados[i]).nombre, ' volvio a responder. ', FormatDateTime(formatofechasConMsecs, now));
      agregarEnOrdenDeVelocidad(nodosDisponibles, nodo);
      nodosBaneados[i]:= NIL;
    end;
  end;
  nodosBaneados.Pack;

  for i:= 0 to nodosOcupados.Count - 1 do
  begin
    nodo:= nodosOcupados[i];
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(nodo.dtUltimoPedido);
{$ELSE}
    Windows.QueryPerformanceCounter(nodo.dtUltimoPedido);
{$ENDIF}
    if preguntarEstadoNodo(nodo.idNodo, nomAplic) = 0 then
    begin
      nodosDisponibles.Add(nodo);
      nodosOcupados[i]:= NIL;
    end;
  end;
  nodosOcupados.Pack;
  if conjuntoDeEjecucion <> NIL then
    actualizarDatosNodos;
  smf_Procesando.Release;
  smf_Procesando.Free;
end;

procedure TDespachador.getTopoLocal();
var
  idTopo: Cardinal;
begin
  idTopo:= lanzarTopoLocal;

  if idTopo <> 0 then
  begin
    idNodoLocal:= getIdNodoLocal;
    listaTopos.Add(TFichaNodo.Create(idNodoLocal, idTopo, pm^.nombreMaquina));
    Writeln('Nodo encontrado ' + pm^.nombreMaquina + ', idNodo= ' + IntToStr(idNodoLocal) + ', idTopo= ' + IntToStr(idTopo) + '...');
  end
  else
    Raise Exception.Create('Error obteniendo topo local');
end;

function sortNodosById(Item1, Item2: Pointer) : Integer;
begin
  if TFichaNodo(Item1).idNodo < TFichaNodo(Item2).idNodo then
    result:= 1
  else if TFichaNodo(Item1).idNodo = TFichaNodo(Item2).idNodo then
    result:= 0
  else
    result := -1;
end;

{procedure TDespachador.getListaTopos();
var
  idPeticion: Cardinal;
  comunicado: TFichaComunicado;

  datosEntrantes: TBuffReader;

  k: Integer;
  nodos: TDAofNCardinal;
  idNodo, idTopo: Cardinal;
begin
  writeln('Buscando topos activos...');
  getTopoLocal;
  comunicado.idNodoOrigen:= idNodoLocal;
  comunicado.idOrigen:= idAplicYo;
  comunicado.idNodoDestino:= idNodoLocal;
  comunicado.idDestino:= getIdAplicacion(comunicado.idNodoDestino, uconstantes.AppName_Topo);
  comunicado.codigoMsg:= uconstantes.MSGP_GETNODOS;
  comunicado.nBytesDatos:= 0;
  idPeticion:= comunicarTS(@comunicado, nil, 20000);
  if idPeticion > 0 then
  begin
    if leerFichaComunicado(idPeticion, @comunicado) > 0 then
    begin
      datosEntrantes:= TBuffReader.Create(comunicado.pdatos, comunicado.nBytesDatos);
      datosEntrantes.xTDAOfCardinal(nodos);
      datosEntrantes.Free;
      if levantarDatosComunicado(idPeticion, nil, 0) > 0 then
      begin
        for k:= 0 to listaTopos.Count - 1 do
          TFichaNodo(listaTopos[k]).Free;
        listaTopos.Clear;
        for k:= 0 to high(nodos) do
        begin
          idNodo:= nodos[k];
          if getNodo(idNodo) = NIL then
          begin
            idTopo:= getIdAplicacion(idNodo, uconstantes.AppName_Topo);
            if idTopo > 0 then
            begin
              listaTopos.Add(TFichaNodo.Create(idNodo, idTopo));
              Writeln('Nodo encontrado, idNodo= ' + IntToStr(idNodo) + ', idTopo= ' + IntToStr(idTopo) + '...');
            end
            else
              writeln('El Nodo en idNodo= ' + IntToStr(idNodo) + ' no respondió' + '...');
          end;
        end;

        Writeln('Fin getListaTopos.');
      end
      else
      begin
        logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_LOS_DATOS_DEL_COMUNICADO_DESDE_LA_DLL));
      end;
    end
    else
    begin
      logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_EL_COMUNICADO_DESDE_LA_DLL));
    end;
  end
  else
    logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_LA_LISTA_DE_TOPOS));
//Asi queda el nodo local primero y le da tiempo a procesar las
//llamadas mientras el resto se esta registrando
  listaTopos.Sort(sortNodosById);
  nodosDisponibles.Clear;
  nodosDisponibles.Capacity:= listaTopos.Count;
  for k:= 0 to listaTopos.Count - 1 do
    nodosDisponibles.Add(listaTopos[k]);
  nodosOcupados.Clear;
  nodosOcupados.Capacity:= listaTopos.Count;
  nodosBaneados.Clear;
  nodosBaneados.Capacity:= listaTopos.Count;
end;}

procedure TDespachador.getListaTopos();
var
  k: Integer;
  idNodo, idTopo: Cardinal;
  datosConexiones: TListaDatosConexionNodo;
  datosConexion: TDatosConexionNodo;
begin
  for k:= 0 to listaTopos.Count - 1 do
    TFichaNodo(listaTopos[k]).Free;
  listaTopos.Count:= 0;

  writeln('Buscando topos activos...');
  getTopoLocal;
  datosConexiones:= TListaDatosConexionNodo.Create_ReadFromText(archiListadoDeTopos);

  for k := 0 to datosConexiones.Count - 1 do
  begin
    datosConexion:= datosConexiones[k];
    if not datosConexion.comentado then
    begin
      if (datosConexion.IPaddr <> '') then
      begin
        idNodo:= IP4StrToCardinal(datosConexion.IPaddr);
        if getNodo(idNodo) = NIL then
        begin
          if getNodo(datosConexion.HostName) <> NIL then
            raise Exception.Create('Hay un nodo con dos IPs distintos y el mismo nombre, revise el archivo con la lista de topos y asegurese de tener una única tarjeta de red funcionando en el nodo local.');
          idTopo:= getIdAplicacion(idNodo, uconstantes_nettopos.AppName_Topo);
          if idTopo > 0 then
          begin
            listaTopos.Add(TFichaNodo.Create(idNodo, idTopo, datosConexion.HostName));
            Writeln('Nodo encontrado ' + datosConexion.HostName + ', idNodo= ' + IntToStr(idNodo) + ', idTopo= ' + IntToStr(idTopo) + '...');
          end
          else
            writeln('El Nodo ' + datosConexion.HostName + ' en idNodo= ' + IntToStr(idNodo) + ' no respondió' + '...');
        end;
      end;
    end;
  end;
  datosConexiones.FreeConElemenentos;

  Writeln('Fin getListaTopos.');
//Asi queda el nodo local primero y le da tiempo a procesar las
//llamadas mientras el resto se esta registrando
  listaTopos.Sort(sortNodosById);
  nodosDisponibles.Clear;
  nodosDisponibles.Capacity:= listaTopos.Count;
  for k:= 0 to listaTopos.Count - 1 do
  begin
    nodosDisponibles.Add(listaTopos[k]);
  end;
  nodosOcupados.Clear;
  nodosOcupados.Capacity:= listaTopos.Count;
  nodosBaneados.Clear;
  nodosBaneados.Capacity:= listaTopos.Count;
  factorDeAumentoEnTOsPorMultiplesNodos:= 1 + 0.1 * (listaTopos.Count -1);
end;

procedure TDespachador.despacharTareasSinEnviar;
var
  i: Integer;
  tarea: TFichaTarea;
  nodo: TFichaNodo;
  idServicio: Cardinal;
begin
  //De las primeras nTareasAEnviar envio primero las que tengan un nodoEjecutor
  //forzado, asi no ocupo esos nodos con tareas que puedan ejecutarse en cualquier
  //otro. Luego las que tengan recomendado (idNodoEjecutor <> 0 y
  //forzarNodoEjecutor = false) y finalmente las que puedan ejecutar en cualquiera
  i:= 0;
  while (i < colaTareasSinEnviar.Count) and
        (nodosDisponibles.Count > 0) and
        (TFichaTarea(colaTareasSinEnviar[i]).forzarNodoEjecutor) do
  begin
    tarea:= colaTareasSinEnviar[i];
    nodo:= getNodoDisponible(tarea.idNodoEjecutor);
    if (nodo = NIL) and (preguntarEstadoNodo(tarea.idNodoEjecutor, tarea.nomServicio) = 0) then
      nodo:= getNodo(tarea.idNodoEjecutor);

    if nodo <> NIL then
    begin
      idServicio:= nodo.idServicio(tarea.nomServicio);
{$IFNDEF WINDOWS}
      timeLib.QueryPerformanceCounter(tarea.dtIniCom);
{$ELSE}
      Windows.QueryPerformanceCounter(tarea.dtIniCom);
{$ENDIF}
      nodo.dtUltimoPedido:= tarea.dtIniCom;
      if enviarPedidoEjecucion(tarea, nodo.idNodo, idServicio) > 0 then
      begin
{$IFNDEF WINDOWS}
        timeLib.QueryPerformanceCounter(tarea.dtIniEjec);
{$ELSE}
        Windows.QueryPerformanceCounter(tarea.dtIniEjec);
{$ENDIF}
        tarea.tiempoCom:= (tarea.dtIniEjec - tarea.dtIniCom) * invFrecuencia;
        tarea.estado:= En_Ejecucion;
        tarea.dtVencimiento:= tarea.dtIniCom + trunc(tarea.timeOutMSecs * frecuencia);
        nodo.dtVencimientoGlobal:= dtVencimientoGlobal;
        agregarEnOrdenDeTimeOut(colaTareasEnviadas, tarea);
        nodosOcupados.Add(nodo);
      end
      else
      begin
        colaFallosDeComunicacion.Add(tarea);
        colaNodosFallosDeComunicacion.Add(nodo);
      end;
      nodosDisponibles.Remove(nodo);
    end
    else
    begin
      colaFallosDeComunicacion.Add(tarea);
    end;
    colaTareasSinEnviar[i]:= NIL;
    i:= i + 1;
  end;

  while (i < colaTareasSinEnviar.Count) and
        (nodosDisponibles.Count > 0) and
        (TFichaTarea(colaTareasSinEnviar[i]).idNodoEjecutor <> 0) do
  begin
    tarea:= colaTareasSinEnviar[i];
    nodo:= getNodoDisponible(tarea.idNodoEjecutor);
    if (nodo = NIL) and (preguntarEstadoNodo(tarea.idNodoEjecutor, tarea.nomServicio) = 0) then
      nodo:= getNodo(tarea.idNodoEjecutor);

    tarea.idNodoEjecutor:= 0;//ya hice el intento de enviarsela al nodo que se me pidio
    if nodo <> NIL then
    begin
      idServicio:= nodo.idServicio(tarea.nomServicio);
{$IFNDEF WINDOWS}
      timeLib.QueryPerformanceCounter(tarea.dtIniCom);
{$ELSE}
      Windows.QueryPerformanceCounter(tarea.dtIniCom);
{$ENDIF}
      nodo.dtUltimoPedido:= tarea.dtIniCom;
      if enviarPedidoEjecucion(tarea, nodo.idNodo, idServicio) > 0 then
      begin
{$IFNDEF WINDOWS}
        timeLib.QueryPerformanceCounter(tarea.dtIniEjec);
{$ELSE}
        Windows.QueryPerformanceCounter(tarea.dtIniEjec);
{$ENDIF}
        tarea.tiempoCom:= (tarea.dtIniEjec - tarea.dtIniCom) * invFrecuencia;
        tarea.estado:= En_Ejecucion;
        tarea.dtVencimiento:= tarea.dtIniCom + trunc(tarea.timeOutMSecs * frecuencia);
        nodo.dtVencimientoGlobal:= dtVencimientoGlobal;
        agregarEnOrdenDeTimeOut(colaTareasEnviadas, tarea);
        nodosOcupados.Add(nodo);
      end
      else
      begin
        colaFallosDeComunicacion.Add(tarea);
        colaNodosFallosDeComunicacion.Add(nodo);
      end;
      nodosDisponibles.Remove(nodo);
    end
    else
    begin
      agregarTareaSinEnviar(colaTareasSinEnviar, tarea);
    end;
    colaTareasSinEnviar[i]:= NIL;
    i:= i + 1;
  end;

  while (i < colaTareasSinEnviar.Count) and
        (nodosDisponibles.Count > 0) do
  begin
    tarea:= colaTareasSinEnviar[i];
    nodo:= nodosDisponibles.Last;
    idServicio:= nodo.idServicio(tarea.nomServicio);
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(tarea.dtIniCom);
{$ELSE}
    Windows.QueryPerformanceCounter(tarea.dtIniCom);
{$ENDIF}
    nodo.dtUltimoPedido:= tarea.dtIniCom;
    if enviarPedidoEjecucion(tarea, nodo.idNodo, idServicio) > 0 then
    begin
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(tarea.dtIniEjec);
{$ELSE}
    Windows.QueryPerformanceCounter(tarea.dtIniEjec);
{$ENDIF}
      tarea.tiempoCom:= (tarea.dtIniEjec - tarea.dtIniCom) * invFrecuencia;
      tarea.estado:= En_Ejecucion;
      tarea.dtVencimiento:= tarea.dtIniCom + trunc(tarea.timeOutMSecs * frecuencia);
      nodo.dtVencimientoGlobal:= dtVencimientoGlobal;
      agregarEnOrdenDeTimeOut(colaTareasEnviadas, tarea);
      nodosOcupados.Add(nodo);
    end
    else
    begin
      colaFallosDeComunicacion.Add(tarea);
      colaNodosFallosDeComunicacion.Add(nodo);
    end;
    nodosDisponibles.Delete(nodosDisponibles.Count - 1);
    colaTareasSinEnviar[i]:= NIL;
    i:= i + 1;
  end;
  colaTareasSinEnviar.Pack;

  if colaFallosDeComunicacion.Count > 0 then
  begin
    for i:= 0 to colaFallosDeComunicacion.Count - 1 do
      agregarTareaSinEnviar(colaTareasSinEnviar, colaFallosDeComunicacion[i]);
    colaFallosDeComunicacion.Count:= 0;
    for i:= 0 to colaNodosFallosDeComunicacion.Count - 1 do
      agregarEnOrdenDeVelocidad(nodosDisponibles, colaNodosFallosDeComunicacion[i]);
    colaNodosFallosDeComunicacion.Count:= 0;
  end;
end;

function TDespachador.enviarPedidoEjecucion(tarea: TFichaTarea; idNodo, idAplic: Cardinal): Cardinal;
var
  resRespuesta, nIntentosComunicacion: Integer;
  timeOutCom: Cardinal;
begin
  with tarea.comunicado do
  begin
    idNodoOrigen:= idNodoLocal;
    idOrigen:= idAplicYo;
    idNodoDestino:= idNodo;
    idDestino:= idAplic;
  end;
  if idAplic = 0 then raise Exception.Create('TDespachador.enviarPedidoEjecucion: Pidieron enviar una tarea a una aplicación con id = 0');
  timeOutCom:= trunc(timeOutComsCte + tarea.comunicado.nBytesDatos * timeOutComsPorByte);

  nIntentosComunicacion:= 0;
  repeat
    resRespuesta:= comunicar(@tarea.comunicado, tarea.comunicado.pdatos, timeOutCom);
    inc(nIntentosComunicacion);
  until (resRespuesta <> 0) or (nIntentosComunicacion = MAX_REINTENTOS_COMUNICACION);
  if resRespuesta = 0 then
    logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_COMUNICAR_LA_TAREA));
  result:= resRespuesta;
end;

{$IFDEF CONLOG}
procedure conlog( s: string; const msg: TMessage );
begin
  writeln(now(), 'clog> ('+s+'), wp:'+IntToStr( msg.wparam )+', lp: '+IntToStr( msg.lparam ) );
end;
{$ENDIF}

procedure TDespachador.recibirRespuesta(var msj: TMessage);
var
  comunicado: TFichaComunicado;
  datosEntrantes: TBuffReader;
  datosLocales: PLArrOfBytes;
{$IFDEF CONLOG}
  idTarea: Cardinal;
{$ENDIF}
begin
{$IFDEF CONLOG}
  conlog('RecibiRespuesta...begin', msj);
  idTarea:= 0;
{$ENDIF}
  if leerFichaComunicado(msj.WParam, @comunicado) > 0 then
  begin
{$IFDEF CONLOG}
    idTarea:= comunicado.idTarea;
{$ENDIF}
    if comunicado.nBytesDatos <> 0 then
    begin
      GetMem(datosLocales, comunicado.nBytesDatos);
      datosEntrantes:= TBuffReader.Create(comunicado.pdatos, comunicado.nBytesDatos);
      datosEntrantes.xBytes(datosLocales^, comunicado.nBytesDatos);
      datosEntrantes.Free;
    end
    else
      datosLocales:= NIL;

    if levantarDatosComunicado(msj.WParam, nil, 0) > 0 then
      procesarRespuesta(comunicado, TBuffReader.Create(datosLocales, comunicado.nBytesDatos))
    else
      logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_LOS_DATOS_DEL_COMUNICADO_DESDE_LA_DLL));
  end
  else
    logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_EL_COMUNICADO_DESDE_LA_DLL));
{$IFDEF CONLOG}
  conlog('RecibiRespuesta...end ' + IntToStr(idTarea), msj);
{$ENDIF}
end;

procedure TDespachador.procesarRespuesta(comunicado: TFichaComunicado; resultados: TBuffReader);
var
  tarea: TFichaTarea;
  iFichaTarea: Integer;
  nodo: TFichaNodo;
{$IFDEF WINDOWS}
  eventoRespuesta: TEvent;
{$ELSE}
  eventoRespuesta: TEventTX;
{$ENDIF}
  smf_Procesando: TMutex;
  ahora: Int64;
begin
  if multiHilo then
  begin
{$IFDEF WINDOWS}
    smf_Procesando:= TMutex.Create(nom_smf_Procesando);
{$ELSE}
    smf_Procesando:= TMutex.Create(nom_smf_Procesando, 1);
{$ENDIF}
    if not smf_Procesando.Get(timeOutSems) then
    begin
      smf_Procesando.Free;
      raise Exception.Create('TDespachador.procesarRespuesta: Error obteniendo el semaforo 4');
    end;
  end
  else
    smf_Procesando:= NIL;

  iFichaTarea:= getIFichaTareaEnviada(comunicado.idTarea);
  if iFichaTarea <> - 1 then
  begin
    tarea:= colaTareasEnviadas[iFichaTarea];
    colaTareasEnviadas.Delete(iFichaTarea);
    tarea.estado:= Terminada;
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
    tarea.tiempoEjec:= (ahora - tarea.dtIniEjec) * invFrecuencia;
    tarea.resultados:= resultados;
    tarea.idNodoEjecutor:= comunicado.idNodoOrigen;
    if tarea.usarTareaParaMedirVelocidad then
		begin
{      writeln(tarea.tiempoCom + tarea.tiempoEjec);
      if (tarea.tiempoCom + tarea.tiempoEjec) < asumaCero then
        tarea.tiempoCom:= tarea.tiempoCom;}

      temporizador.addMedicion(comunicado.idNodoOrigen, tarea.pesoRelativo / (tarea.tiempoCom + tarea.tiempoEjec));
      huboActualizacionTiempos:= true;
    end;
  end
  else
  begin
//    dumpEstadoDespachador;
    logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_ENCUENTRA_LA_TAREA) + ', idTarea= ' + IntToStr(comunicado.idTarea));
  end;

  nodo:= getNodo(comunicado.idNodoOrigen);
  if nodo <> NIL then
  begin
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
    if (ahora < nodo.dtVencimientoGlobal) then  //O-->D
    begin
      if nodosDisponibles.IndexOf(nodo) = -1 then
        agregarEnOrdenDeVelocidad(nodosDisponibles, nodo);
      nodosOcupados.Remove(nodo);
      despacharTareasSinEnviar;
    end
    else //O-->B
    begin
      if nodosBaneados.IndexOf(nodo) = -1 then
      begin
        writeln('Nodo ', nodo.nombre, ' baneado. ', FormatDateTime(formatofechasConMsecs, now));
        nodosBaneados.Add(nodo);
      end;
      nodosOcupados.Remove(nodo);
    end;
  end
  else
    raise Exception.Create('Recibi respuesta de un nodo desconocido. IdNodo= ' + IntToStr(comunicado.idNodoOrigen));

  if multiHilo then
  begin
    smf_Procesando.Release;
    smf_Procesando.Free;

    if iFichaTarea = 0 then
    begin
{$IFDEF WINDOWS}
      eventoRespuesta:= TEvent.Create(nom_EventoRespuesta, false);
{$ELSE}
      eventoRespuesta:= TEventTX.Create(nom_EventoRespuesta, 1);
{$ENDIF}
      eventoRespuesta.Signal;
      eventoRespuesta.Free;
    end;
  end
  else
    if iFichaTarea = 0 then
      flg_evento:= true;
end;

procedure TDespachador.Free;
var
  i: Integer;
begin
{$IFNDEF WINDOWS}
  SysUtils.DeleteFile(nom_smf_Procesando);
  SysUtils.DeleteFile(nom_EventoRespuesta);
{$ENDIF}
  if listaTopos <> NIL then
  begin
    for i:= 0 to listaTopos.Count - 1 do
      TFichaNodo(listaTopos[i]).Free;
    listaTopos.Free;
  end;
  if colaTareasSinEnviar <> NIL then
  begin
    for i:= 0 to colaTareasSinEnviar.Count - 1 do
      TFichaTarea(colaTareasSinEnviar[i]).Free;
    colaTareasSinEnviar.Free;
  end;
  if colaTareasEnviadas <> NIL then
  begin
    for i:= 0 to colaTareasEnviadas.Count - 1 do
      TFichaTarea(colaTareasEnviadas[i]).Free;
    colaTareasEnviadas.Free;
  end;
  if nodosDisponibles <> NIL then
    nodosDisponibles.Free;
  if nodosOcupados <> NIL then
    nodosOcupados.Free;
  if nodosBaneados <> NIL then
    nodosBaneados.Free;
  if nodosBaneadosPermanentemente <> NIL then
    nodosBaneadosPermanentemente.Free;
  if colaFallosDeComunicacion <> NIL then
    colaFallosDeComunicacion.Free;
  if colaNodosFallosDeComunicacion <> NIL then
    colaNodosFallosDeComunicacion.Free;
  if ejecuteElTopo then
    finalizarEjecuciones(uconstantes_nettopos.AppName_Topo);

  inherited Free;
end;

procedure TDespachador.banearNodoPermanentemente(idNodo: Cardinal);
var
  nodo: TFichaNodo;
  i, iNodo: Integer;
begin
  nodo:= getNodo(idNodo);
  iNodo:= nodosBaneados.IndexOf(nodo);
  if iNodo <> -1 then
    nodosBaneados.Delete(iNodo)
  else
  begin
    iNodo:= nodosDisponibles.IndexOf(nodo);
    if iNodo <> -1 then
      nodosDisponibles.Delete(iNodo)
    else
      Raise Exception.Create('TDespachador.banearNodoPermanentemente: me pidieron banear permanentemente un nodo que no esta ni disponible ni baneado. IdNodo= ' + IntToStr(idNodo));
  end;
  nodosBaneadosPermanentemente.Add(nodo);

  if (conjuntoDeEjecucion <> nil) and (conjuntoDeEjecucion.Count > 0) then
  begin
    iNodo:= -1;
    for i:= 0 to conjuntoDeEjecucion.Count - 1 do
      if TDatosNodo(conjuntoDeEjecucion[i]).idNodo = idNodo then
      begin
        iNodo:= i;
        TDatosNodo(conjuntoDeEjecucion[i]).disponible:= false;
        break;
      end;
    if iNodo <> -1 then
    begin
      conjuntoDeEjecucion.Delete(iNodo);
      if temporizador <> NIL then
        temporizador.eliminarMedicion(iNodo);
    end
    else
      Raise Exception.Create('TDespachador.banearNodoPermanentemente: me pidieron banear permanentemente un nodo para el que no tengo datos. IdNodo= ' + IntToStr(idNodo));
  end;
end;

procedure TDespachador.reasignarTareasVencidas;
var
  tarea: TFichaTarea;
  nodo: TFichaNodo;
  i: Integer;
  ahora: Int64;
begin
  i:= 0;
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
  while (i < colaTareasEnviadas.Count) and
        (TFichaTarea(colaTareasEnviadas[i]).dtVencimiento <= ahora) do
  begin
    tarea:= colaTareasEnviadas[i];
    colaTareasEnviadas[i]:= NIL;
    agregarTareaSinEnviar(colaTareasSinEnviar, tarea);
    tarea.estado:= Esperando_Despacho;
    nodo:= getNodo(tarea.comunicado.idNodoDestino);
    if preguntarEstadoNodo(nodo.idNodo, tarea.nomServicio) = 0 then//Si esta esperando mensajes
    begin
      nodosOcupados.Remove(nodo);
      agregarEnOrdenDeVelocidad(nodosDisponibles, nodo);
    end;
    i:= i + 1;
    logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_TIMEOUT_VENCIDO_PARA_LA_TAREA) +
             ', idTarea= ' + IntToStr(tarea.comunicado.idTarea) + ', nodoDestino= '
             + IntToStr(tarea.comunicado.idNodoDestino));
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
  end;

  colaTareasEnviadas.Pack;
  despacharTareasSinEnviar;
end;

function TDespachador.preguntarEstadoNodo(idNodo: Cardinal; nomAplic: String): Cardinal;
var
  nomAplicAsPChar: PAnsiChar;
  idAplic: Cardinal;
begin
  GetMem(nomAplicAsPChar, length(nomAplic) + 1);
  StrPCopy(nomAplicAsPChar, nomAplic);
  idAplic:= getIdAplicacion(idNodo, nomAplicAsPChar);
  FreeMem(nomAplicAsPChar, length(nomAplic) + 1);
  if idAplic <> 0 then
    result:= preguntarEstadoNodo(idNodo, idAplic)
  else
    result:= Maxint;
end;

function TDespachador.preguntarEstadoNodo(idNodo, idAplic: Cardinal): Cardinal;
var
  comunicado: TFichaComunicado;
  resRespuesta, nIntentosComunicacion: Integer;
  datosEntrantes: TBuffReader;
  res: Cardinal;
begin
  comunicado.idNodoOrigen:= idNodoLocal;
  comunicado.idOrigen:= idAplicYo;
  comunicado.idNodoDestino:= idNodo;
  comunicado.idDestino:= idAplic;
  comunicado.codigoMsg:= uMsgsDespachador.MSGP_ESTADO;
  comunicado.nBytesDatos:= 0;
  comunicado.pdatos:= nil;

  nIntentosComunicacion:= 0;
  repeat
    resRespuesta:= comunicarTS(@comunicado, comunicado.pdatos, 100);
    inc(nIntentosComunicacion);
  until (resRespuesta <> 0) or (nIntentosComunicacion = MAX_REINTENTOS_COMUNICACION);
  if resRespuesta = 0 then
  begin
    logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_PREGUNTAR_EL_ESTADO));
    result:= Maxint;
  end
  else
  begin
    if leerFichaComunicado(resRespuesta, @comunicado) > 0 then
    begin
      datosEntrantes:= TBuffReader.Create(comunicado.pdatos, comunicado.nBytesDatos);
      datosEntrantes.xCardinal(res);
      datosEntrantes.Free;
      if levantarDatosComunicado(resRespuesta, nil, 0) > 0 then
      begin
        result:= res;
      end
      else
      begin
        logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_LOS_DATOS_DEL_COMUNICADO_DESDE_LA_DLL));
        result:= MaxInt;
      end;
    end
    else
    begin
      logError(uMsgsDespachador.codErrorToString(uMsgsDespachador.ERROR_NO_SE_PUDO_TRAER_EL_COMUNICADO_DESDE_LA_DLL));
      result:= Maxint;
    end;
  end;
end;

procedure TDespachador.actualizarDatosNodos;
var
  i, j: Integer;
  nodo: TFichaNodo;
  datosNodo: TDatosNodo;
begin
  conjuntoDeEjecucion.nNodosDisponibles:= nodosDisponibles.Count;
  for i:= 0 to nodosDisponibles.Count - 1 do
  begin
    nodo:= TFichaNodo(nodosDisponibles[i]);
    for j:= 0 to conjuntoDeEjecucion.Count - 1 do
      if nodo.idNodo = TDatosNodo(conjuntoDeEjecucion[j]).idNodo then
      begin
        datosNodo:= conjuntoDeEjecucion[j];

        //Aca actualizo
        datosNodo.disponible:= true;
        break;
      end;
  end;

  for i:= 0 to nodosOcupados.Count - 1 do
  begin
    nodo:= TFichaNodo(nodosOcupados[i]);
    for j:= 0 to conjuntoDeEjecucion.Count - 1 do
      if nodo.idNodo = TDatosNodo(conjuntoDeEjecucion[j]).idNodo then
      begin
        datosNodo:= conjuntoDeEjecucion[j];

        //Aca actualizo
        datosNodo.disponible:= false;
        break;
      end;
  end;

  for i:= 0 to nodosBaneados.Count - 1 do
  begin
    nodo:= TFichaNodo(nodosBaneados[i]);
    for j:= 0 to conjuntoDeEjecucion.Count - 1 do
      if nodo.idNodo = TDatosNodo(conjuntoDeEjecucion[j]).idNodo then
      begin
        datosNodo:= conjuntoDeEjecucion[j];

        //Aca actualizo
        datosNodo.disponible:= false;
        break;
      end;
  end;
end;

procedure TDespachador.dumpEstadoDespachador;
var
  i: Integer;
  fDump: TextFile;
  smf_Procesando: TMutex;
  archi: String;
  teniaElSemaforo: boolean;
  ahora: int64;
begin
  archi:= DateTimeToStr( now ()) + '_dump_Variables.txt';
  while pos( '/', archi ) > 0 do
    archi[pos('/', archi )]:= '-';
  while pos( ':', archi ) > 0 do
    archi[pos(':', archi )]:= '-';
  assignfile(fDump, archi);
  Rewrite(fDump);
{$IFDEF WINDOWS}
  smf_Procesando:= TMutex.Create(nom_smf_Procesando);
{$ELSE}
  smf_Procesando:= TMutex.Create(nom_smf_Procesando, 1);
{$ENDIF}

  teniaElSemaforo:= smf_Procesando.Release; //barrabasada, si mi hilo tiene el semaforo lo suelto antes de seguir y lo vuelvo a pedir
  try
    if not smf_Procesando.Get(1000) then
      raise Exception.Create('TDespachador.dumpEstadoDespachador: No pude obtener el semaforo para dump');
{$IFNDEF WINDOWS}
    timeLib.QueryPerformanceCounter(ahora);
{$ELSE}
    Windows.QueryPerformanceCounter(ahora);
{$ENDIF}
    writeln(fDump, 'ahora= ', ahora);
    writeln(fDump, 'iniForkAndJoin= ', iniForkAndJoin);
    writeln(fDump, 'dtVencimientoGlobal= ', dtVencimientoGlobal);
    Writeln(fDump, 'idNodoLocal= ', idNodoLocal);
    Writeln(fDump, 'idAplicYo= ', idAplicYo);
    Writeln(fDump, 'ejecuteElTopo= ', ejecuteElTopo);
    Writeln(fDump, 'nextTimeOut= ', nextTimeOut);
    Writeln(fDump, 'listaTopos:');
    writeln(fDump, 'count: ', listaTopos.Count);
    for i:= 0 to listaTopos.Count - 1 do
    begin
      TFichaNodo(listaTopos[i]).dumpToText(fDump, charIndentacion);
      writeln(fDump);
    end;
    writeln(fDump);

    Writeln(fDump, 'nodosDisponibles:');
    writeln(fDump, 'count: ', nodosDisponibles.Count);
    for i:= 0 to nodosDisponibles.Count - 1 do
    begin
      writeln(fDump, 'idNodo= ', TFichaNodo(nodosDisponibles[i]).nombre, charIndentacion);
    end;
    Writeln(fDump);

    Writeln(fDump, 'nodosOcupados:');
    writeln(fDump, 'count: ', nodosOcupados.Count);
    for i:= 0 to nodosOcupados.Count - 1 do
    begin
      writeln(fDump, 'idNodo= ', TFichaNodo(nodosOcupados[i]).nombre, charIndentacion);
    end;
    Writeln(fDump);

    Writeln(fDump, 'nodosBaneados:');
    writeln(fDump, 'count: ', nodosBaneados.Count);
    for i:= 0 to nodosBaneados.Count - 1 do
    begin
      writeln(fDump, 'idNodo= ', TFichaNodo(nodosBaneados[i]).nombre, charIndentacion);
    end;
    Writeln(fDump);

    Writeln(fDump, 'colaTareasSinEnviar:');
    writeln(fDump, 'count: ', colaTareasSinEnviar.Count);
    for i:= 0 to colaTareasSinEnviar.Count - 1 do
    begin
      TFichaTarea(colaTareasSinEnviar[i]).dumpToText(fDump, charIndentacion);
      writeln(fDump);
    end;
    Writeln(fDump);

    Writeln(fDump, 'colaTareasEnviadas:');
    writeln(fDump, 'count: ', colaTareasEnviadas.Count);
    for i:= 0 to colaTareasEnviadas.Count - 1 do
    begin
      TFichaTarea(colaTareasEnviadas[i]).dumpToText(fDump, charIndentacion);
      writeln(fDump);
    end;
    Writeln(fDump);
  finally
    if not teniaElSemaforo then
      smf_Procesando.Release;
    smf_Procesando.Free;
    CloseFile(fDump);
  end;
end;

function TDespachador.getNodo(nombreNodo: String): TFichaNodo;
var
  i: Integer;
  res: TFichaNodo;
begin
  res:= NIL;
  nombreNodo:= AnsiLowerCase(nombreNodo);
  for i:= 0 to listaTopos.Count - 1 do
    if TFichaNodo(listaTopos[i]).nombre = nombreNodo then
    begin
      res:= listaTopos[i];
      break;
    end;
  result:= res;
end;

function TDespachador.getNodo(idNodo: Cardinal): TFichaNodo;
var
  i: Integer;
  res: TFichaNodo;
begin
  res:= NIL;
  //Si es el nodo local
  if (idNodo = 0) or (idNodo = 255) then
    idNodo:= getIdNodoLocal;
  for i:= 0 to listaTopos.Count - 1 do
    if TFichaNodo(listaTopos[i]).idNodo = idNodo then
    begin
      res:= listaTopos[i];
      break;
    end;
  result:= res;
end;

function TDespachador.getNodoDisponible(idNodo: Cardinal): TFichaNodo;
var
  i: Integer;
  res: TFichaNodo;
begin
  res:= NIL;
  //Si es el nodo local
  if (idNodo = 0) or (idNodo = 255) then
    idNodo:= getIdNodoLocal;
  for i:= 0 to nodosDisponibles.Count - 1 do
    if TFichaNodo(nodosDisponibles[i]).idNodo = idNodo then
    begin
      res:= nodosDisponibles[i];
      break;
    end;
  result:= res;
end;

{function TDespachador.getFichaTarea(idTarea: Cardinal): TFichaTarea;
var
  i: Integer;
  res: TFichaTarea;
begin
  res:= NIL;
  for i:= 0 to colaTareasSinEnviar.Count - 1 do
    if TFichaTarea(colaTareasSinEnviar[i]).comunicado.idTarea = idTarea then
    begin
      res:= colaTareasSinEnviar[i];
      break;
    end;
  result:= res;
end;}

function TDespachador.getIFichaTareaEnviada(idTarea: Cardinal): Integer;
var
  i, res: Integer;
begin
  res:= -1;
  for i:= 0 to colaTareasEnviadas.Count - 1 do
    if TFichaTarea(colaTareasEnviadas[i]).comunicado.idTarea = idTarea then
    begin
      res:= i;
      break;
    end;
  result:= res;
end;

function agregarEnOrdenDeTimeOut(lista: TList; tareaEnviada: TFichaTarea): Integer;
var
  i: Integer;
begin
  i:= 0;
  while (i < lista.Count) and
        (TFichaTarea(lista[i]).dtVencimiento <= tareaEnviada.dtVencimiento) do
    i:= i + 1;
  lista.Insert(i, tareaEnviada);
  result:= i;
end;

function agregarEnOrdenDeVelocidad(lista: TList; nodo: TFichaNodo): Integer;
var
  i: Integer;
begin
  i:= 0;
  while (i < lista.Count) and
        (TFichaNodo(lista[i]).velocidad <= nodo.velocidad) do
    i:= i + 1;
  lista.Insert(i, nodo);
  result:= i;
end;

procedure agregarTareaSinEnviar(lista: TList; tarea: TFichaTarea);
var
  i: Integer;
begin
  if tarea.forzarNodoEjecutor then
  begin
    //Primero las forzadas
    i:= 0;
    while (i < lista.Count) and
          (TFichaTarea(lista[i]).forzarNodoEjecutor) and
          (TFichaTarea(lista[i]).pesoRelativo >= tarea.pesoRelativo) do
      i:= i + 1;
    lista.Insert(i, tarea);
  end
  else if tarea.idNodoEjecutor <> 0 then
  begin
    //Luego las recomendadas
    i:= 0;
    while (i < lista.Count) and
          (TFichaTarea(lista[i]).forzarNodoEjecutor) do
      i:= i + 1;
    while (i < lista.Count) and
          (TFichaTarea(lista[i]).idNodoEjecutor <> 0) and
          (TFichaTarea(lista[i]).pesoRelativo >= tarea.pesoRelativo) do
      i:= i + 1;
    lista.Insert(i, tarea);
  end
  else
  begin
    //Finalmente las que pueden ejecutar en cualquier lado
    i:= 0;
    while (i < lista.Count) and
          (TFichaTarea(lista[i]).forzarNodoEjecutor) do
      i:= i + 1;
    while (i < lista.Count) and
          (TFichaTarea(lista[i]).idNodoEjecutor <> 0) do
      i:= i + 1;
    while (i < lista.Count) and
          (TFichaTarea(lista[i]).pesoRelativo >= tarea.pesoRelativo) do
      i:= i + 1;
    lista.Insert(i, tarea);
  end;
end;

function sortFichaNodoByVelocidad(Item1, Item2: Pointer): Integer;
begin
  if TFichaNodo(Item1).velocidad < TFichaNodo(Item2).velocidad then
    result:= -1
  else if TFichaNodo(Item1).velocidad = TFichaNodo(Item2).velocidad then
    result:= 0
  else       
    result:= 1;
end;

end.
