Archive: Hangs during nsDialogs timer execution


Hangs during nsDialogs timer execution
I've run into a problem while making a custom page (using nsDialogs). The page mimics InstFiles except that it is used to display progress of copying files from one directory to another using list of files embed into installer. Copying files is done in loop. Page is displayed prior to InstFiles.

The problem is that after a few seconds the page is displayed the window hangs up in "not responding" mode, although copying goes on fine and if I'd wait till the end it would resume back.

I've tested without copying any files and using dummy "Sleep" commands but it made no difference. Apparently if the process in CreateTimer takes more than a few seconds the installer window becomes unresponsive.

Is there any way of preventing it from doing so?

NSIS 2.46, Windows 7 x64

Here is code for the page:


!define /math PBM_SETRANGE32 ${WM_USER} + 6
!define FILES_COUNT 2400

Page custom CopyFilesPage

Var CopyFilesPage.Dialog
Var CopyFilesPage.Status
Var CopyFilesPage.ProgressBar

Function CopyFilesPage
nsDialogs::Create 1018
Pop $CopyFilesPage.Dialog

!insertmacro MUI_HEADER_TEXT "Copy files" "Description"

${NSD_CreateLabel} 0 0 450 15 ""
Pop $CopyFilesPage.Status

${NSD_CreateProgressBar} 0 16 450 17 ""
Pop $CopyFilesPage.ProgressBar

${NSD_CreateTimer} CopyFiles 10
nsDialogs::Show
FunctionEnd


And here is the actual file copy function:


Function CopyFiles
${NSD_KillTimer} CopyFiles

InitPluginsDir
File /oname=$PLUGINSDIR\files.md5 "texts\files.md5"
FileOpen $R0 $PLUGINSDIR\files.md5 r

IfErrors 0 start
MessageBox MB_OK|MB_ICONSTOP $(INSTALL_FOLDER_NOACCESS)
Abort

start:
ClearErrors
StrCpy $9 0 ; Checking toggle
StrCpy $8 1 ; File counter
SendMessage $CopyFilesPage.ProgressBar ${PBM_SETRANGE32} 0 "${FILES_COUNT}"

loop: ; Read next file from the list and check if it exists
FileRead $R0 $1
IfErrors done

IntOp $8 $8 + 1

StrCpy $2 $1 32
StrCpy $3 $1 "" 34
${StrFilter} $3 "" "" "$\n$\r" $3

FileWrite $R1 "*$3$\r$\n"

IfFileExists "$FL_INSTDIR\$3" check
MessageBox MB_OK|MB_ICONSTOP $(COPY_FILE_MISSING)
FileWrite $R1 "File missing!$\r$\n"
Goto stop

check: ; Get file MD5 signature and compare it to the value from the list
StrCmp $9 1 copy ; Checking disabled, copy file
StrCmp $2 "--------------------------------" copy ; Don't need to verify file, copy it
md5dll::GetMD5File "$SOURCEDIR\$3"
Pop $4

StrCmp $2 $4 copy ; MD5 matched, copy it
MessageBox MB_YESNO|MB_ICONEXCLAMATION $(COPY_FILE_CHECK_FAIL) IDYES disableCheck IDNO
Goto stop

copy: ; Copy the file to new installation directory
CopyFiles /SILENT $SOURCEDIR\$3 $INSTDIR\$3
${NSD_SetText} $CopyFilesPage.Status "$3"
SendMessage $CopyFilesPage.ProgressBar ${PBM_SETPOS} "$8" 0
Goto loop

disableCheck: ; Disable checking
StrCpy $9 1
Goto loop

stop: ; Abort installation
FileClose $R0
Abort

done:
FileClose $R0
FunctionEnd


I suspect that I need tell NSIS to postpone some sort of window timeout so that window wouldn't hang in "not responsive" mode, but how do I do that and were in code?

Here is simplified code to replicate the behavior:

!include MUI2.nsh
!include WinMessages.nsh

ShowInstDetails show
RequestExecutionLevel user

!define /math PBM_SETRANGE32 ${WM_USER} + 6
!define MUI_FINISHPAGE_NOAUTOCLOSE

Name "Copy files!"
OutFile "copyfiles.exe"

Page custom copyPage copyFiles
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_LANGUAGE English

Section
DetailPrint "Done here!"
SectionEnd

Var PageCopyFiles
Var PageCopyFiles.ProgressBar
Var PageCopyFiles.Status

Function copyPage
StrCpy $R0 150 ; Numbers of files
StrCpy $R1 1 ; Counter starts at one

nsDialogs::Create 1018
Pop $PageCopyFiles

!insertmacro MUI_HEADER_TEXT "Copying Files" "Time to hang up!"

${NSD_CreateLabel} 0 0 450 15 ""
Pop $PageCopyFiles.Status
${NSD_CreateProgressBar} 0 16 450 17 ""
Pop $PageCopyFiles.ProgressBar
SendMessage $PageCopyFiles.ProgressBar ${PBM_SETRANGE32} 0 $R0
nsDialogs::Show
FunctionEnd

Function copyFiles
loop:
IntCmp $R1 $R0 done 0 done
IntOp $R1 $R1 + 1
${NSD_SetText} $PageCopyFiles.Status "Copy $R1"
SendMessage $PageCopyFiles.ProgressBar ${PBM_SETPOS} "$R1" 0
Sleep 100
Goto loop
done:
${NSD_SetText} $PageCopyFiles.Status "Copy complete."
FunctionEnd


Freezes at "Copy 55" or so but will unfreeze once it finishes.

I can see problem in your loop.
You call the copyFiels function in the same thread as GUI runs, there may be a catch.
Try to create separate thread (= plugin) which will copy files and create callback for updating progress bar.

If copying is fine and it freezes only on point 55 there may be some problem with sleep implementation?


Well, I've been tinkering further trying to find solutions and came by with another one for my particular case.

It seems a little dirty trick, but it did work for me. I removed custom page for copying files. I put file copy function into a hidden section above all others, then I modified UI with resourcehacker to add extra progress bar above the normal one. During file copy function I update the progressbar above as files being copied, once complete I hide it so that primary progress bar is visible again and installer continues as normal extracting files displaying extraction progress.

So from what I can gather anything executed in sections does not make installer halt if the process is taking long time. I suppose it is because code in sections run in a separate thread. If so then it is a logical explanation why my previous attempt froze installer window although continued to install - it took all the thread just for itself. But while I did figure a workaround for my specific case I would like to know for the future how do I run functions outside GUI thread to do some heavy processing in custom pages and how do I use callbacks to update some element inside GUI, such as progress bar or text labels.


i had a very similar problem and it was only caused by a variable that was already in use. i haven't really looked into your script, but maybe my solution works for you as well.