home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / cenvi23.zip / UNHANG.CMM < prev    next >
Text File  |  1995-02-08  |  13KB  |  386 lines

  1. //****************************************************************
  2. //*** UnHang.cmm - CEnvi program to monitor PM queues and kill ***
  3. //*** ver.3        or otherwise handle session that have hung  ***
  4. //***              queues to allow the system to continue.     ***
  5. //****************************************************************
  6.  
  7. #include <OptParms.lib>
  8.  
  9. main(argc,argv)
  10. {
  11.    ParseInputArgs(argc,argv);
  12.    if ( 1 != argc ) {
  13.       // All the args weren't parsed, and so invalid input.
  14.       // show how to use this program.
  15.       Instructions();
  16.    } else {
  17.  
  18.       if ( defined(LineCount) )
  19.          system("mode 80,%d",LineCount);
  20.       if ( HighPriority )
  21.          RunAtHighPriority();
  22.       if ( Hide )
  23.          HideMyself();
  24.       UnhangForever();
  25.    }
  26. }
  27.  
  28. #define DEFAULT_MAX_HANGTIME       60     // how many seconds, max (approximately)
  29.                                   // can a program hang before we kill it
  30. #define DEFAULT_CYCLES_PER_MAX_HANGTIME 3 // break test down to this many times
  31.                                   // maximum hang time, more cycles gives
  32.                                   // each program a better chance to not
  33.                                   // be hung, but takes up more time
  34. #define DEFAULT_MINIMUM_SUSPEND_FACTOR   0  // don't suspend less than this long
  35.  
  36.  
  37. Instructions()
  38. {
  39.    printf("\a\n");
  40.    printf("UnHang.cmm - Prevent the workplace shell from hanging due to a bad app.\n");
  41.    printf("\n");
  42.    printf("SYNTAX: CEnvi2 UnHang.cmm [Options]\n");
  43.    printf("\n");
  44.    printf("Options: /LINES=linecount - how many lines to display in the output\n");
  45.    printf("         /ONTOP - keep this screen on top of other windows\n");
  46.    printf("         /HIDE - Hide this window\n");
  47.    printf("         /LOG=FileSpec - Filename to log hangs and attempts to unhang\n");
  48.    printf("         /HIGH - Run at high priority\n");
  49.    printf("         /TIMEOUT=# - how many seconds (max) for complete test; default %d\n", DEFAULT_MAX_HANGTIME);
  50.    printf("         /CYCLES=# - how many mini-test cycles per timeout; default %d\n", DEFAULT_CYCLES_PER_MAX_HANGTIME);
  51.    printf("         /SUSPEND=# - minimum time to suspend between checks; this keeps from\n");
  52.    printf("                      \"hogging\" the cpu; 0 is bad if /HIGH; default %d\n",DEFAULT_MINIMUM_SUSPEND_FACTOR);
  53.    printf("         /COMMAND=cmd - will call this command if an application seems hung,\n");
  54.    printf("                        sending Process ID as first parameter and process name\n");
  55.    printf("                        as second parameter. Default is to kill the app.\n");
  56.    printf("\n");
  57. }
  58.  
  59. LineCount;
  60. OnTop;
  61. Hide;
  62. LogFileName;
  63. HighPriority;
  64. MaxHangtime = DEFAULT_MAX_HANGTIME;
  65. CyclesPerMaxHangtime = DEFAULT_CYCLES_PER_MAX_HANGTIME;
  66. MinimumSuspendFactor = DEFAULT_MINIMUM_SUSPEND_FACTOR;
  67. HangCommand;
  68.  
  69. ParseInputArgs(argc,argv)
  70. {
  71.    if ( OptionalParameter(argc,argv,"LINES",lTemp)
  72.      && (LineCount=atoi(lTemp)) < 1 )
  73.       printf("Invalid LINES parameter\a\n"), abort();
  74.    OnTop = OptionalParameter(argc,argv,"ONTOP");
  75.    Hide = OptionalParameter(argc,argv,"HIDE");
  76.    OptionalParameter(argc,argv,"LOG",LogFileName);
  77.    HighPriority = OptionalParameter(argc,argv,"HIGH");
  78.    if ( OptionalParameter(argc,argv,"TIMEOUT",lTemp)
  79.      && (MaxHangtime=atoi(lTemp)) < 1 )
  80.       printf("Invalid TIMEOUT parameter\a\n"), abort();
  81.    if ( OptionalParameter(argc,argv,"CYCLES",lTemp)
  82.      && (CyclesPerMaxHangtime=atoi(lTemp)) < 1 )
  83.       printf("Invalid CYCLES parameter\a\n"), abort();
  84.    if ( OptionalParameter(argc,argv,"SUSPEND",lTemp)
  85.      && (MinimumSuspendFactor=atoi(lTemp)) < 0 )
  86.       printf("Invalid SUSPEND parameter\a\n"), abort();
  87.    OptionalParameter(argc,argv,"COMMAND",HangCommand);
  88. }
  89.  
  90.  
  91. /////////////////////////////////////////////////////////////////////////
  92.  
  93. #define HWND_DESKTOP 1
  94. #define HWND_OBJECT  2
  95.  
  96. SuspendFactor;
  97.  
  98. UnhangForever()
  99. {
  100.    // initialize suspend factor - 25 sounds like a good number to start with
  101.    SuspendFactor = max(25,MinimumSuspendFactor);
  102.  
  103.    // initialize timers to know loops are run frequently enough
  104.    TimeBetweenLoops = MaxHangtime / CyclesPerMaxHangtime ;
  105.    PMShellProblemTime = 0; // PMSHELL gets treated special; we'll let it have
  106.                            // a little extra hung time to allow some other
  107.                            // process to hang first
  108.    PMShellTimeout = MaxHangtime * (CyclesPerMaxHangtime + 2) / CyclesPerMaxHangtime;
  109.  
  110.    for( ; ; ) { // FOREVER
  111.  
  112.       LoopStartTime = time();
  113.  
  114.       if ( OnTop )
  115.          PutMyselfOnTop();
  116.  
  117.       // build an array of all the queue from the desktop on down
  118.       QueueList = BuildQueueList();
  119.       PList = ProcessList();
  120.  
  121.       // Since it does no good to kill the very first queue, then don't test it
  122.       //QueueList++;
  123.  
  124.       // For each queue, see if it is hung
  125.       printf("\n\nProcess      ID    Queue   Size   Status\n");
  126.       printf("---------- ------  -----  ------  ------");
  127.       PMShellProblem = False;
  128.       for ( q = GetArraySpan(QueueList); 0 <= q; q-- ) {
  129.  
  130.          hmq = QueueList[q];
  131.          if SuspendFactor suspend(SuspendFactor);
  132.  
  133.          if ( GetQueueData(hmq,PList,ProcessName,ProcessID,QueueSize) ) {
  134.             printf("\n%-10.10s   %-5d %04X    %-6d  ",ProcessName,ProcessID,hmq,QueueSize);
  135.             if ( HungQueue(hmq) ) {
  136.                printf("HUNG");
  137.  
  138.                // If this is PMSHELL, then give it a little extra time
  139.                if ( !strcmpi("PMSHELL",ProcessName) ) {
  140.                   PMShellProblem = True;
  141.                   if ( 0 == PMShellProblemTime )
  142.                      PMShellProblemTime = time();
  143.                   if ( difftime(time(),PMShellProblemTime) < PMShellTimeout )
  144.                      continue;
  145.                }
  146.                LogFile("Process %s, id = %d, queue = %04X, size = %d, hung\n",
  147.                        ProcessName,ProcessID,hmq,QueueSize);
  148.                AttendToHungProcess(ProcessID,ProcessName);
  149.                printf(" AND KILLED!\a\a\a");
  150.             } else {
  151.                printf("OK");
  152.             }
  153.          }
  154.  
  155.       }
  156.       if ( !PMShellProblem )
  157.          PMShellProblemTime = 0;
  158.  
  159.       // Adjust SuspendFactor to try to even-out processor time used by UNHANG.
  160.       // If extra time is available then increase SuspendFactor.  If not
  161.       // enough time is available then decrease it.
  162.       ElapsedTestTime = difftime(time(),LoopStartTime);
  163.       ExcessTime = TimeBetweenLoops - ElapsedTestTime;
  164.       SuspendFactor = max(MinimumSuspendFactor,SuspendFactor + ExcessTime/CyclesPerMaxHangtime);
  165.       if ( 0 < ExcessTime ) {
  166.          // a little time left over, so wait until time is up
  167.          while ( difftime(time(),LoopStartTime) < TimeBetweenLoops )
  168.             suspend(SuspendFactor);
  169.       }
  170.       printf("\n   New SuspendFactor = %d (from excess %d)\n",SuspendFactor,ExcessTime);
  171.    }
  172. }
  173.  
  174.  
  175. LogFile(Format)
  176. {
  177.    if ( defined(LogFileName) ) {
  178.       if ( fp = fopen(LogFileName,"a") ) {
  179.          fprintf(fp,"\n");
  180.          fprintf(fp,ctime(time()));
  181.          va_start(valist,Format);
  182.          vfprintf(fp,Format,valist);
  183.          va_end(valist);
  184.          fclose(fp);
  185.       }
  186.    }
  187. }
  188.  
  189. BuildQueueList() // build array of all desktop window handles
  190. {
  191.    AddQueueForWindowAndChildren(QList,HWND_DESKTOP);
  192.    //AddQueueForWindowAndChildren(QList,HWND_OBJECT);
  193.    return(QList);
  194. }
  195.  
  196. AddQueueForWindowAndChildren(QList,ParentHandle)
  197. {
  198.    // Get the queue for this parent handle
  199.    Queue = GetWindowQueue(ParentHandle);
  200.  
  201.    // If this queue is not already in the queue list, then add it
  202.    if ( !defined(QList) ) {
  203.       QList[0] = Queue;
  204.    } else {
  205.       for ( i = GetArraySpan(QList); 0 <= i; i-- ) {
  206.          if ( Queue == QList[i] )
  207.             break;
  208.       }
  209.       if ( i < 0 )
  210.          QList[1+GetArraySpan(QList)] = Queue;
  211.    }
  212.  
  213.    // recursively call this routine for all its children
  214.    EnumHandle = WinBeginEnumWindows(ParentHandle);
  215.    while ( NULL != (WinHandle = WinGetNextWindow(EnumHandle)) ) {
  216.       if SuspendFactor suspend(SuspendFactor);
  217.       AddQueueForWindowAndChildren(QList,WinHandle);
  218.    }
  219.    WinEndEnumWindows(EnumHandle);
  220.    if SuspendFactor suspend(SuspendFactor);
  221. }
  222.  
  223.  
  224. HungQueue(hmq) // return True if this queue is hung
  225. {
  226.    if ( !GetQueueInfo(hmq,MsgCount,ProcessID) )
  227.       // this no longer appears to be a queue; and so never mind
  228.       return(False);
  229.  
  230.    // determine if queue is hung by sending lots of NULL message to it
  231.    // then wait a second and if sending null message fails then it is hung
  232.    MsgCountPerLoop = MsgCount / CyclesPerMaxHangtime ;
  233.    for ( i = MsgCountPerLoop; 0 < i--; ) {
  234.       if SuspendFactor suspend(SuspendFactor);
  235.       if ( !PostNullMsgToQueue(hmq) ) {
  236.          // there appears to have been an error posting a null message.
  237.          // and so the queue must be full (or no longer valid).  Wait
  238.          // some seconds and give it one more chance
  239.          suspend( 5 * 1000 );
  240.          return( !PostNullMsgToQueue(hmq)
  241.               && GetQueueInfo(hmq,MsgCount,ProcessID) );
  242.       }
  243.    }
  244.    return(False);
  245. }
  246.  
  247. WinBeginEnumWindows(pHwndParent)
  248. {
  249.    #define ORD_WIN32BEGINENUMWINDOWS   702
  250.    return DynamicLink("PMWIN",ORD_WIN32BEGINENUMWINDOWS,BIT32,CDECL,pHwndParent)
  251. }
  252.  
  253. WinGetNextWindow(pHEnum)
  254. {
  255.    #define ORD_WIN32GETNEXTWINDOW      756
  256.    return DynamicLink("PMWIN",ORD_WIN32GETNEXTWINDOW,BIT32,CDECL,pHEnum)
  257. }
  258.  
  259. WinEndEnumWindows(pHEnum)
  260. {
  261.    #define ORD_WIN32ENDENUMWINDOWS     737
  262.    return DynamicLink("PMWIN",ORD_WIN32ENDENUMWINDOWS,BIT32,CDECL,pHEnum)
  263. }
  264.  
  265. GetWindowQueue(hwnd)
  266. {
  267.    #define ORD_WIN32QUERYWINDOWULONG   843
  268.    #define QWL_HMQ   (-4)
  269.    return DynamicLink("PMWIN",ORD_WIN32QUERYWINDOWULONG,BIT32,CDECL,
  270.                       hwnd,QWL_HMQ)
  271. }
  272.  
  273. GetQueueInfo(hmq,count,pid)
  274. {
  275.    #define ORD_WIN32QUERYQUEUEINFO  824
  276.    BLOBSize(qinfo,4 * 5);
  277.    success = DynamicLink("PMWIN",ORD_WIN32QUERYQUEUEINFO,BIT32,CDECL,
  278.                          hmq,qinfo,BLObSize(qinfo));
  279.    if ( success ) {
  280.       pid = BLObGet(qinfo,4 * 1,UWORD32);
  281.       count = BLObGet(qinfo,4 * 3,UWORD32);
  282.    }
  283.    return(success);
  284. }
  285.  
  286. GetQueueData(hmq,PList,ProcessName,ProcessID,QueueSize)
  287.    // return data about this queue and who owns it; False if cannot
  288. {
  289.    if ( !GetQueueInfo(hmq,QueueSize,ProcessID) )
  290.       return(False);
  291.  
  292.    // to find process name, match processID to id in PList
  293.    for ( p = GetArraySpan(PList); 0 <= p; p-- ) {
  294.       if ( ProcessID == PList[p].id ) {
  295.          ProcessName = SplitFileName(PList[p].name).name;
  296.          return(True);
  297.       }
  298.    }
  299.    // if got here, then all OK except for process name
  300.    ProcessName = "?????";
  301.    return(True);
  302. }
  303.  
  304. PostNullMsgToQueue(hmq)
  305. {
  306.    #define ORD_WIN32POSTQUEUEMSG 902
  307.    #define WM_NULL   0
  308.    return DynamicLink("PMWIN",ORD_WIN32POSTQUEUEMSG,BIT32,CDECL,
  309.                       hmq,WM_NULL,MessageID,0,0)
  310. }
  311.  
  312. gDeadList;  // keep a list of processes already killed
  313. AttendToHungProcess(pId,pName)
  314. {
  315.    // determine if this process already (attempted) killed
  316.    if ( !defined(gDeadList) ) {
  317.       gDeadList[0] = pId;
  318.    } else {
  319.       for ( lIdx = GetArraySpan(gDeadList); 0 <= lIdx; lIdx-- ) {
  320.          if ( gDeadList[lIdx] == pId )
  321.             break;
  322.       }
  323.       if ( lIdx < 0 )
  324.          // this pId hasn't been killed before; add to list
  325.          gDeadList[GetArraySpan(gDeadList)+1] = pId;
  326.       else
  327.          // this pId has been killed before, and so try to kill its parent
  328.          AttendToHungProcessParent(pId);
  329.    }
  330.  
  331.    if ( !defined(HangCommand) ) {
  332.       #define DKP_PROCESSTREE    0  // kill process and all descendents, if created by this process
  333.       #define DKP_PROCESS        1  // kill any process even if not created by this process
  334.       #define ORD_DOS32KILLPROCESS  235
  335.       DynamicLink("doscalls",ORD_DOS32KILLPROCESS,BIT32,CDECL,DKP_PROCESS,pId);
  336.    } else {
  337.       system("%s %d %s",HangCommand,pId,pName);
  338.    }
  339. }
  340.  
  341. AttendToHungProcessParent(id)
  342. {
  343.    // get parent of this id, and then KILL IT
  344.    list = ProcessList();
  345.    // find entry for this process ID
  346.    for ( proc = GetArraySpan(list); 0 <= proc; proc-- ) {
  347.       if ( list[proc].id == id ) {
  348.          parent = list[proc].parent;
  349.          for ( _i = GetArraySpan(list); 0 <= _i; _i-- ) {
  350.             if ( list[_i].id == parent ) {
  351.                LogFile("Kill Parent Process %s, id = %d\n",
  352.                        list[_i].name,parent);
  353.                printf(" (Kill Parent %s, id = %d) ",
  354.                       list[_i].name,parent);
  355.                AttendToHungProcess(parent,list[_i].name);
  356.             }
  357.          }
  358.       }
  359.    }
  360. }
  361.  
  362. RunAtHighPriority() // make this a high priority process
  363. {
  364.    #define ORD_DOS32SETPRIORITY  236
  365.    #define PRTYC_TIMECRITICAL 3
  366.    DynamicLink("doscalls",ORD_DOS32SETPRIORITY,BIT32,CDECL,0,PRTYC_TIMECRITICAL,0,0)
  367. }
  368.  
  369. HideMyself()
  370. {
  371.    #define SWP_HIDE              0x0010
  372.    #define ORD_WIN32SETWINDOWPOS 875
  373.    DynamicLink("PMWIN",ORD_WIN32SETWINDOWPOS,BIT32,CDECL,
  374.                Info().WinHandle,0,0,0,0,0,SWP_HIDE);
  375. }
  376.  
  377. PutMyselfOnTop()
  378. {
  379.    #define HWND_TOP     3
  380.    #define SWP_ZORDER   4
  381.    #define ORD_WIN32SETWINDOWPOS 875
  382.    DynamicLink("PMWIN",ORD_WIN32SETWINDOWPOS,BIT32,CDECL,
  383.                Info().WinHandle,HWND_TOP,0,0,0,0,SWP_ZORDER);
  384. }
  385.  
  386.