Remake
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
Functions
Server

Functions

static void complete_job (int job_id, bool success)
 
static std::string prepare_script (rule_t const &rule)
 
static bool run_script (int job_id, rule_t const &rule)
 
static bool start (std::string const &target, client_list::iterator &current)
 
static void complete_request (client_t &client, bool success)
 
static bool has_free_slots ()
 
static bool handle_clients ()
 
static void create_server ()
 
void accept_client ()
 
void finalize_job (pid_t pid, bool res)
 
void server_loop ()
 
void server_mode (std::string const &remakefile, string_list const &targets)
 

Detailed Description

Function Documentation

void accept_client ( )

Accept a connection from a client, get the job it spawned from, get the targets, and mark them as dependencies of the job targets.

Definition at line 2419 of file remake.cpp.

Referenced by server_loop().

2420 {
2421  DEBUG_open << "Handling client request... ";
2422 
2423  // Accept connection.
2424 #ifdef WINDOWS
2425  socket_t fd = accept(socket_fd, NULL, NULL);
2426  if (fd == INVALID_SOCKET) return;
2427  if (!SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, 0))
2428  {
2429  error2:
2430  std::cerr << "Unexpected failure while setting connection with client" << std::endl;
2431  closesocket(fd);
2432  return;
2433  }
2434  // WSAEventSelect puts sockets into nonblocking mode, so disable it here.
2435  u_long nbio = 0;
2436  if (ioctlsocket(fd, FIONBIO, &nbio)) goto error2;
2437 #elif defined(LINUX)
2438  int fd = accept4(socket_fd, NULL, NULL, SOCK_CLOEXEC);
2439  if (fd < 0) return;
2440 #else
2441  int fd = accept(socket_fd, NULL, NULL);
2442  if (fd < 0) return;
2443  if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) return;
2444 #endif
2445  clients.push_front(client_t());
2446  client_list::iterator proc = clients.begin();
2447 
2448  if (false)
2449  {
2450  error:
2451  DEBUG_close << "failed\n";
2452  std::cerr << "Received an ill-formed client message" << std::endl;
2453  #ifdef WINDOWS
2454  closesocket(fd);
2455  #else
2456  close(fd);
2457  #endif
2458  clients.erase(proc);
2459  return;
2460  }
2461 
2462  // Receive message. Stop when encountering two nuls in a row.
2463  std::vector<char> buf;
2464  size_t len = 0;
2465  while (len < sizeof(int) + 2 || buf[len - 1] || buf[len - 2])
2466  {
2467  buf.resize(len + 1024);
2468  ssize_t l = recv(fd, &buf[0] + len, 1024, 0);
2469  if (l <= 0) goto error;
2470  len += l;
2471  }
2472 
2473  // Parse job that spawned the client.
2474  int job_id;
2475  memcpy(&job_id, &buf[0], sizeof(int));
2476  proc->socket = fd;
2477  proc->job_id = job_id;
2478  job_targets_map::const_iterator i = job_targets.find(job_id);
2479  if (i == job_targets.end()) goto error;
2480  DEBUG << "receiving request from job " << job_id << std::endl;
2481 
2482  // Parse the targets and mark them as dependencies from the job targets.
2483  dependency_t &dep = *dependencies[job_targets[job_id].front()];
2484  char const *p = &buf[0] + sizeof(int);
2485  while (true)
2486  {
2487  len = strlen(p);
2488  if (len == 0)
2489  {
2490  ++waiting_jobs;
2491  return;
2492  }
2493  std::string target(p, p + len);
2494  DEBUG << "adding dependency " << target << " to job\n";
2495  proc->pending.push_back(target);
2496  dep.deps.insert(target);
2497  p += len + 1;
2498  }
2499 }
static void complete_job ( int  job_id,
bool  success 
)
static

Handle job completion.

Definition at line 1915 of file remake.cpp.

Referenced by complete_request(), finalize_job(), and run_script().

1916 {
1917  DEBUG_open << "Completing job " << job_id << "... ";
1918  job_targets_map::iterator i = job_targets.find(job_id);
1919  assert(i != job_targets.end());
1920  string_list const &targets = i->second;
1921  if (success)
1922  {
1923  for (string_list::const_iterator j = targets.begin(),
1924  j_end = targets.end(); j != j_end; ++j)
1925  {
1926  update_status(*j);
1927  }
1928  }
1929  else
1930  {
1931  DEBUG_close << "failed\n";
1932  std::cerr << "Failed to build";
1933  for (string_list::const_iterator j = targets.begin(),
1934  j_end = targets.end(); j != j_end; ++j)
1935  {
1936  status[*j].status = Failed;
1937  std::cerr << ' ' << *j;
1938  remove(j->c_str());
1939  }
1940  std::cerr << std::endl;
1941  }
1942  job_targets.erase(i);
1943 }
static void complete_request ( client_t client,
bool  success 
)
static

Send a reply to a client then remove it. If the client was a dependency client, start the actual script.

Definition at line 2185 of file remake.cpp.

Referenced by handle_clients().

2186 {
2187  DEBUG_open << "Completing request from client of job " << client.job_id << "... ";
2188  if (client.delayed)
2189  {
2190  assert(client.socket == INVALID_SOCKET);
2191  if (success)
2192  {
2193  if (still_need_rebuild(client.delayed->targets.front()))
2194  run_script(client.job_id, *client.delayed);
2195  else complete_job(client.job_id, true);
2196  }
2197  else complete_job(client.job_id, false);
2198  delete client.delayed;
2199  }
2200  else if (client.socket != INVALID_SOCKET)
2201  {
2202  char res = success ? 1 : 0;
2203  send(client.socket, &res, 1, MSG_NOSIGNAL);
2204  #ifdef WINDOWS
2205  closesocket(client.socket);
2206  #else
2207  close(client.socket);
2208  #endif
2209  --waiting_jobs;
2210  }
2211 
2212  if (client.job_id < 0 && !success) build_failure = true;
2213 }
static void create_server ( )
static

Create a named unix socket that listens for build requests. Also set the REMAKE_SOCKET environment variable that will be inherited by all the job scripts.

Definition at line 2339 of file remake.cpp.

Referenced by server_mode().

2340 {
2341  if (false)
2342  {
2343  error:
2344  perror("Failed to create server");
2345 #ifndef WINDOWS
2346  error2:
2347 #endif
2348  exit(EXIT_FAILURE);
2349  }
2350  DEBUG_open << "Creating server... ";
2351 
2352 #ifdef WINDOWS
2353  // Prepare a windows socket.
2354  struct sockaddr_in socket_addr;
2355  socket_addr.sin_family = AF_INET;
2356  socket_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
2357  socket_addr.sin_port = 0;
2358 
2359  // Create and listen to the socket.
2360  socket_fd = socket(AF_INET, SOCK_STREAM, 0);
2361  if (socket_fd == INVALID_SOCKET) goto error;
2362  if (!SetHandleInformation((HANDLE)socket_fd, HANDLE_FLAG_INHERIT, 0))
2363  goto error;
2364  if (bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(sockaddr_in)))
2365  goto error;
2366  int len = sizeof(sockaddr_in);
2367  if (getsockname(socket_fd, (struct sockaddr *)&socket_addr, &len))
2368  goto error;
2369  std::ostringstream buf;
2370  buf << socket_addr.sin_port;
2371  if (!SetEnvironmentVariable("REMAKE_SOCKET", buf.str().c_str()))
2372  goto error;
2373  if (listen(socket_fd, 1000)) goto error;
2374 #else
2375  // Set signal handlers for SIGCHLD and SIGINT.
2376  // Block SIGCHLD (unblocked during select).
2377  sigset_t sigmask;
2378  sigemptyset(&sigmask);
2379  sigaddset(&sigmask, SIGCHLD);
2380  if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) goto error;
2381  struct sigaction sa;
2382  sa.sa_flags = 0;
2383  sigemptyset(&sa.sa_mask);
2384  sa.sa_handler = &sigchld_handler;
2385  if (sigaction(SIGCHLD, &sa, NULL) == -1) goto error;
2386  sa.sa_handler = &sigint_handler;
2387  if (sigaction(SIGINT, &sa, NULL) == -1) goto error;
2388 
2389  // Prepare a named unix socket in temporary directory.
2390  socket_name = tempnam(NULL, "rmk-");
2391  if (!socket_name) goto error2;
2392  struct sockaddr_un socket_addr;
2393  size_t len = strlen(socket_name);
2394  if (len >= sizeof(socket_addr.sun_path) - 1) goto error2;
2395  socket_addr.sun_family = AF_UNIX;
2396  strcpy(socket_addr.sun_path, socket_name);
2397  len += sizeof(socket_addr.sun_family);
2398  if (setenv("REMAKE_SOCKET", socket_name, 1)) goto error;
2399 
2400  // Create and listen to the socket.
2401 #ifdef LINUX
2402  socket_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
2403  if (socket_fd == INVALID_SOCKET) goto error;
2404 #else
2405  socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
2406  if (socket_fd == INVALID_SOCKET) goto error;
2407  if (fcntl(socket_fd, F_SETFD, FD_CLOEXEC) < 0) goto error;
2408 #endif
2409  if (bind(socket_fd, (struct sockaddr *)&socket_addr, len))
2410  goto error;
2411  if (listen(socket_fd, 1000)) goto error;
2412 #endif
2413 }
void finalize_job ( pid_t  pid,
bool  res 
)

Handle child process exit status.

Definition at line 2504 of file remake.cpp.

Referenced by server_loop().

2505 {
2506  pid_job_map::iterator i = job_pids.find(pid);
2507  assert(i != job_pids.end());
2508  int job_id = i->second;
2509  job_pids.erase(i);
2510  --running_jobs;
2511  complete_job(job_id, res);
2512 }
static bool handle_clients ( )
static

Handle client requests:

  • check for running targets that have finished,
  • start as many pending targets as allowed,
  • complete the request if there are neither running nor pending targets left or if any of them failed.
Returns
true if some child processes are still running.
Postcondition
If there are pending requests, at least one child process is running.

Definition at line 2235 of file remake.cpp.

Referenced by server_loop().

2236 {
2237  DEBUG_open << "Handling client requests... ";
2238  restart:
2239 
2240  for (client_list::iterator i = clients.begin(), i_next = i,
2241  i_end = clients.end(); i != i_end && has_free_slots(); i = i_next)
2242  {
2243  ++i_next;
2244  DEBUG_open << "Handling client from job " << i->job_id << "... ";
2245  if (false)
2246  {
2247  failed:
2248  complete_request(*i, false);
2249  clients.erase(i);
2250  DEBUG_close << "failed\n";
2251  continue;
2252  }
2253 
2254  // Remove running targets that have finished.
2255  for (string_set::iterator j = i->running.begin(), j_next = j,
2256  j_end = i->running.end(); j != j_end; j = j_next)
2257  {
2258  ++j_next;
2259  status_map::const_iterator k = status.find(*j);
2260  assert(k != status.end());
2261  switch (k->second.status)
2262  {
2263  case Running:
2264  break;
2265  case Failed:
2266  if (!keep_going) goto failed;
2267  i->failed = true;
2268  // no break
2269  case Uptodate:
2270  case Remade:
2271  i->running.erase(j);
2272  break;
2273  case Recheck:
2274  case Todo:
2275  assert(false);
2276  }
2277  }
2278 
2279  // Start pending targets.
2280  while (!i->pending.empty())
2281  {
2282  std::string target = i->pending.front();
2283  i->pending.pop_front();
2284  switch (get_status(target).status)
2285  {
2286  case Running:
2287  i->running.insert(target);
2288  break;
2289  case Failed:
2290  pending_failed:
2291  if (!keep_going) goto failed;
2292  i->failed = true;
2293  // no break
2294  case Uptodate:
2295  case Remade:
2296  break;
2297  case Recheck:
2298  case Todo:
2299  client_list::iterator j = i;
2300  if (!start(target, i)) goto pending_failed;
2301  j->running.insert(target);
2302  if (!has_free_slots()) return true;
2303  // Job start might insert a dependency client.
2304  i_next = i;
2305  ++i_next;
2306  break;
2307  }
2308  }
2309 
2310  // Try to complete the request.
2311  // (This might start a new job if it was a dependency client.)
2312  if (i->running.empty())
2313  {
2314  if (i->failed) goto failed;
2315  complete_request(*i, true);
2316  clients.erase(i);
2317  DEBUG_close << "finished\n";
2318  }
2319  }
2320 
2321  if (running_jobs != waiting_jobs) return true;
2322  if (running_jobs == 0 && clients.empty()) return false;
2323 
2324  // There is a circular dependency.
2325  // Try to break it by completing one of the requests.
2326  assert(!clients.empty());
2327  std::cerr << "Circular dependency detected" << std::endl;
2328  client_list::iterator i = clients.begin();
2329  complete_request(*i, false);
2330  clients.erase(i);
2331  goto restart;
2332 }
static bool has_free_slots ( )
static

Return whether there are slots for starting new jobs.

Definition at line 2218 of file remake.cpp.

Referenced by handle_clients().

2219 {
2220  if (max_active_jobs <= 0) return true;
2222 }
static std::string prepare_script ( rule_t const &  rule)
static

Return the script obtained by substituting variables.

Definition at line 1948 of file remake.cpp.

Referenced by run_script().

1949 {
1950  std::string const &s = rule.script;
1951  std::istringstream in(s);
1952  std::ostringstream out;
1953  size_t len = s.size();
1954 
1955  while (!in.eof())
1956  {
1957  size_t pos = in.tellg(), p = s.find('$', pos);
1958  if (p == std::string::npos || p == len - 1) p = len;
1959  out.write(&s[pos], p - pos);
1960  if (p == len) break;
1961  ++p;
1962  switch (s[p])
1963  {
1964  case '$':
1965  out << '$';
1966  in.seekg(p + 1);
1967  break;
1968  case '<':
1969  if (!rule.deps.empty())
1970  out << rule.deps.front();
1971  in.seekg(p + 1);
1972  break;
1973  case '^':
1974  {
1975  bool first = true;
1976  for (string_list::const_iterator i = rule.deps.begin(),
1977  i_end = rule.deps.end(); i != i_end; ++i)
1978  {
1979  if (first) first = false;
1980  else out << ' ';
1981  out << *i;
1982  }
1983  in.seekg(p + 1);
1984  break;
1985  }
1986  case '@':
1987  assert(!rule.targets.empty());
1988  out << rule.targets.front();
1989  in.seekg(p + 1);
1990  break;
1991  case '*':
1992  out << rule.stem;
1993  in.seekg(p + 1);
1994  break;
1995  case '(':
1996  {
1997  in.seekg(p - 1);
1998  bool first = true;
1999  input_generator gen(in, &rule.vars, true);
2000  while (true)
2001  {
2002  std::string w;
2003  input_status s = gen.next(w);
2004  if (s == SyntaxError)
2005  {
2006  // TODO
2007  return "false";
2008  }
2009  if (s == Eof) break;
2010  if (first) first = false;
2011  else out << ' ';
2012  out << w;
2013  }
2014  break;
2015  }
2016  default:
2017  // Let dollars followed by an unrecognized character
2018  // go through. This differs from Make, which would
2019  // use a one-letter variable.
2020  out << '$';
2021  in.seekg(p);
2022  }
2023  }
2024 
2025  return out.str();
2026 }
static bool run_script ( int  job_id,
rule_t const &  rule 
)
static

Execute the script from rule.

Definition at line 2031 of file remake.cpp.

Referenced by complete_request(), and start().

2032 {
2033  if (show_targets)
2034  {
2035  std::cout << "Building";
2036  for (string_list::const_iterator i = rule.targets.begin(),
2037  i_end = rule.targets.end(); i != i_end; ++i)
2038  {
2039  std::cout << ' ' << *i;
2040  }
2041  std::cout << std::endl;
2042  }
2043 
2045  dep->targets = rule.targets;
2046  dep->deps.insert(rule.deps.begin(), rule.deps.end());
2047  for (string_list::const_iterator i = rule.targets.begin(),
2048  i_end = rule.targets.end(); i != i_end; ++i)
2049  {
2050  dependencies[*i] = dep;
2051  }
2052 
2053  std::string script = prepare_script(rule);
2054 
2055  std::ostringstream job_id_buf;
2056  job_id_buf << job_id;
2057  std::string job_id_ = job_id_buf.str();
2058 
2059  DEBUG_open << "Starting script for job " << job_id << "... ";
2060  if (false)
2061  {
2062  error:
2063  DEBUG_close << "failed\n";
2064  complete_job(job_id, false);
2065  return false;
2066  }
2067 
2068 #ifdef WINDOWS
2069  HANDLE pfd[2];
2070  if (false)
2071  {
2072  error2:
2073  CloseHandle(pfd[0]);
2074  CloseHandle(pfd[1]);
2075  goto error;
2076  }
2077  if (!CreatePipe(&pfd[0], &pfd[1], NULL, 0))
2078  goto error;
2079  if (!SetHandleInformation(pfd[0], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
2080  goto error2;
2081  STARTUPINFO si;
2082  ZeroMemory(&si, sizeof(STARTUPINFO));
2083  si.cb = sizeof(STARTUPINFO);
2084  si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
2085  si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
2086  si.hStdInput = pfd[0];
2087  si.dwFlags |= STARTF_USESTDHANDLES;
2088  PROCESS_INFORMATION pi;
2089  ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
2090  if (!SetEnvironmentVariable("REMAKE_JOB_ID", job_id_.c_str()))
2091  goto error2;
2092  char const *argv = echo_scripts ? "SH.EXE -e -s -v" : "SH.EXE -e -s";
2093  if (!CreateProcess(NULL, (char *)argv, NULL, NULL,
2094  true, 0, NULL, NULL, &si, &pi))
2095  {
2096  goto error2;
2097  }
2098  CloseHandle(pi.hThread);
2099  DWORD len = script.length(), wlen;
2100  if (!WriteFile(pfd[1], script.c_str(), len, &wlen, NULL) || wlen < len)
2101  std::cerr << "Unexpected failure while sending script to shell" << std::endl;
2102  CloseHandle(pfd[0]);
2103  CloseHandle(pfd[1]);
2104  ++running_jobs;
2105  job_pids[pi.hProcess] = job_id;
2106  return true;
2107 #else
2108  int pfd[2];
2109  if (false)
2110  {
2111  error2:
2112  close(pfd[0]);
2113  close(pfd[1]);
2114  goto error;
2115  }
2116  if (pipe(pfd) == -1)
2117  goto error;
2118  if (setenv("REMAKE_JOB_ID", job_id_.c_str(), 1))
2119  goto error2;
2120  if (pid_t pid = vfork())
2121  {
2122  if (pid == -1) goto error2;
2123  ssize_t len = script.length();
2124  if (write(pfd[1], script.c_str(), len) < len)
2125  std::cerr << "Unexpected failure while sending script to shell" << std::endl;
2126  close(pfd[0]);
2127  close(pfd[1]);
2128  ++running_jobs;
2129  job_pids[pid] = job_id;
2130  return true;
2131  }
2132  // Child process starts here. Notice the use of vfork above.
2133  char const *argv[5] = { "sh", "-e", "-s", NULL, NULL };
2134  if (echo_scripts) argv[3] = "-v";
2135  if (pfd[0] != 0)
2136  {
2137  dup2(pfd[0], 0);
2138  close(pfd[0]);
2139  }
2140  close(pfd[1]);
2141  execve("/bin/sh", (char **)argv, environ);
2142  _exit(EXIT_FAILURE);
2143 #endif
2144 }
void server_loop ( )

Loop until all the jobs have finished.

Postcondition
There are no client requests left, not even virtual ones.

Definition at line 2519 of file remake.cpp.

Referenced by server_mode().

2520 {
2521  while (handle_clients())
2522  {
2523  DEBUG_open << "Handling events... ";
2524  #ifdef WINDOWS
2525  size_t len = job_pids.size() + 1;
2526  HANDLE h[len];
2527  int num = 0;
2528  for (pid_job_map::const_iterator i = job_pids.begin(),
2529  i_end = job_pids.end(); i != i_end; ++i, ++num)
2530  {
2531  h[num] = i->first;
2532  }
2533  WSAEVENT aev = WSACreateEvent();
2534  h[num] = aev;
2535  WSAEventSelect(socket_fd, aev, FD_ACCEPT);
2536  DWORD w = WaitForMultipleObjects(len, h, false, INFINITE);
2537  WSAEventSelect(socket_fd, aev, 0);
2538  WSACloseEvent(aev);
2539  if (len <= w)
2540  continue;
2541  if (w == len - 1)
2542  {
2543  accept_client();
2544  continue;
2545  }
2546  pid_t pid = h[w];
2547  DWORD s = 0;
2548  bool res = GetExitCodeProcess(pid, &s) && s == 0;
2549  CloseHandle(pid);
2550  finalize_job(pid, res);
2551  #else
2552  sigset_t emptymask;
2553  sigemptyset(&emptymask);
2554  fd_set fdset;
2555  FD_ZERO(&fdset);
2556  FD_SET(socket_fd, &fdset);
2557  int ret = pselect(socket_fd + 1, &fdset, NULL, NULL, NULL, &emptymask);
2558  if (ret > 0 /* && FD_ISSET(socket_fd, &fdset)*/) accept_client();
2559  if (!got_SIGCHLD) continue;
2560  got_SIGCHLD = 0;
2561  pid_t pid;
2562  int status;
2563  while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
2564  {
2565  bool res = WIFEXITED(status) && WEXITSTATUS(status) == 0;
2566  finalize_job(pid, res);
2567  }
2568  #endif
2569  }
2570 
2571  assert(clients.empty());
2572 }
void server_mode ( std::string const &  remakefile,
string_list const &  targets 
)

Load dependencies and rules, listen to client requests, and loop until all the requests have completed. If Remakefile is obsolete, perform a first run with it only, then reload the rules, and perform a second with the original clients.

Definition at line 2580 of file remake.cpp.

Referenced by main().

2581 {
2583  load_rules(remakefile);
2584  create_server();
2585  if (get_status(remakefile).status != Uptodate)
2586  {
2587  clients.push_back(client_t());
2588  clients.back().pending.push_back(remakefile);
2589  server_loop();
2590  if (build_failure) goto early_exit;
2591  variables.clear();
2592  specific_rules.clear();
2593  generic_rules.clear();
2594  first_target.clear();
2595  load_rules(remakefile);
2596  }
2597  clients.push_back(client_t());
2598  if (!targets.empty()) clients.back().pending = targets;
2599  else if (!first_target.empty())
2600  clients.back().pending.push_back(first_target);
2601  server_loop();
2602  early_exit:
2603  close(socket_fd);
2604 #ifndef WINDOWS
2605  remove(socket_name);
2606  free(socket_name);
2607 #endif
2609  exit(build_failure ? EXIT_FAILURE : EXIT_SUCCESS);
2610 }
static bool start ( std::string const &  target,
client_list::iterator &  current 
)
static

Create a job for target according to the loaded rules. Mark all the targets from the rule as running and reset their dependencies. If the rule has dependencies, create a new client to build them just before current, and change current so that it points to it.

Definition at line 2152 of file remake.cpp.

Referenced by handle_clients().

2153 {
2154  DEBUG_open << "Starting job " << job_counter << " for " << target << "... ";
2155  rule_t rule = find_rule(target);
2156  if (rule.targets.empty())
2157  {
2158  status[target].status = Failed;
2159  DEBUG_close << "failed\n";
2160  std::cerr << "No rule for building " << target << std::endl;
2161  return false;
2162  }
2163  for (string_list::const_iterator i = rule.targets.begin(),
2164  i_end = rule.targets.end(); i != i_end; ++i)
2165  {
2166  status[*i].status = Running;
2167  }
2168  int job_id = job_counter++;
2169  job_targets[job_id] = rule.targets;
2170  if (!rule.deps.empty())
2171  {
2172  current = clients.insert(current, client_t());
2173  current->job_id = job_id;
2174  current->pending = rule.deps;
2175  current->delayed = new rule_t(rule);
2176  return true;
2177  }
2178  return run_script(job_id, rule);
2179 }