Debugging a database deadlock - asp-classic

I am trying to solve a problem with a site written in classic ASP with a SQL Server 2000 database.
Every few days the site seems to go down. There is no response from the website when you try to visit it. The loading indicator in your browser will spin round and the page just stays blank.
When I run sp_who2 after the site has gone down there's always a process that has taken up a large amount of CPU time. This process will be blocking all the other processes in the database.
I can get the site working again by killing this process.
I can't work out what's going on. When I look to see the stored procedure that this process ran before it locked up there's nothing wrong with it. The page that runs this stored procedure closes all the connection objects.
Any ideas of what could be causing this deadlock, or how I can stop it from happening?

Not sure if this is the issue, but it could be that not all recordsets and connection are always closed... When we had similar issues in the past we ended up with the following routine.. (Note that this is just a snippet showing one recordset closure, the real procedure actually goes over 15 different recordsets to see if they need to be closed..).
The modCloseObjects() prodedure is then always called at the end of the page, before a redirect, inside error handling and so one...
' subroutine will close and set the objects to Nothing. '
' Close Recordsets and then the Connection '
sub modCloseObjects()
'Close the record sets one by one '
If ucase(TypeName(oRS)) = "RECORDSET" then
if oRS.state <> adStateClosed then
oRS.close
Set oRS = Nothing
end if
end if
' if you have other recordSet objects, add them to the rourtine here: '
' Close the connection '
If ucase(TypeName(objConn)) = "CONNECTION" then
if objConn.state <> adStateClosed then
objConn.close
Set objConn = Nothing
end if
end if
end sub
If you don't have adovbs.inc , you'll need the following constant too:
Const adStateClosed = &H00000000

Related

Execute VB Script on page_load

I am having VB Script in ASPX page.I need to use that Script in codeBehind in Page _load with in a For loop,for each iteration.
My Code is :-
(.ASPX Page with VB Script. )
<script type="text/vbscript" language="vbscript" >
sub wordit()
'Opens Word application and does some process
end sub
</script>
VB Code Behind Part:-
For i As Integer = 1 To colSelRowIndex
CheckboxTemplateId = colSelRowKeys(i).ToString 'I get the ID from here
ViewState("TemplateID") = CheckboxTemplateId 'I need to send the value to the sub routines
hen()'sub
den()'sub
cs.RegisterStartupScript(cstype, csname1 & i, "wordit();", True)
Next
I need to open a word doc for an ID and another document for another ID from the loop.
Try this:
For i As Integer = 1 To 10
cs.RegisterStartupScript(cstype, csname1 & i, "wordit();", True)
Next
That second argument in that function call is looking for a unique key. This is a feature, to prevent accidentally programmatically adding the same script more than once. If you want to do it on purpose, you need a unique key each time.
But that you want to do this at all indicates a possible fundamental misunderstanding about what's going on. While your server code (including Page_Load) is running, your client page in the web browser doesn't exist. The purpose of the server code is always to generate an html response to web request. The server code can never directly manipulate a page DOM.
Obviously this is true for a first request to a page in session: the server must first generate the initial page to send the client. But even on subsequent postbacks, the browser will destroy the prior instance of a page. The server must rebuild the entire page from scratch. Every. Time. While this happens, the page you're looking at in your browser window is only a sort of after-image. The browser has already destroyed any prior DOM, and is waiting for the server to supply a whole new set of HTML.
I also wonder at your use of vbscript, rather than javascript. Using vbscript pretty much guarantees you're page will only work with Internet Explorer.

recordset .eof throwing error when empty

I've got a simple piece of vbscript in a classic asp page which checks the database for entries, and redirects if there are any. Works well if entries exist, but throws errors if there are none. I've done this kind of thing quite a bit, but for some reason it just won't work for me right now and I can't for the life of me figure out why. Here's a snippet of my code:
query = "SELECT idcat FROM categories WHERE affID="&thisAff&";"
rs = conntemp.execute(query)
if not rs.eof then
newCat = rs("idcat")
response.redirect "viewCat.asp?"&newCat
end if
And again, if I give a value for thisAff that has any entries in the database this works fine, but if I give one without entries then rs.eof breaks my code. Any help would be greatly appreciated, as banging my head into my desk doesn't seem to be working.
You have to use set
set rs = conntemp.execute(query)
Use rs.bof to check if the rs is empty, as in:
'if records were returned...
If Not .BOF Then
.MoveFirst
'loop through each record
Do Until .EOF
'PUT YOUR CODE HERE
.MoveNext
Loop
End If

Iterating through IHtmlElementCollection

I have a VB webapplication that needs to read information from an excisting webpage on the internet. Therefore I use the mshtml library. I read the html into an ihtmldocument3 interface. After that I iterate through an ihtmlelementcollection and everything worked fine in Visual Studio 2010 Debugger. At least, the first time. When I debug the code for the second time, after iterating a few elements, the next elements return nothing and I get an exception. (When I break into the code the ihtmlelementcollection shows 0 items.) When I rename all the variables, it runs properly, but again, only the first time.
Here's the code I use to debug. I have outlined the actual code because that responds into an exception (null reference). Do I need to manually release a collection or something or am I doing something stupid?
'global variable
Private tables as IHTMLElementCollection
...........................................
Dim tableChildren As IHTMLElementCollection = tables(3).children
Dim trElements As IHTMLElementCollection = tableChildren.item(0).getElementsByTagName("tr")
Dim intCount As Integer 'just for debugging purposes
For Each element As IHTMLElement In trElements
intCount += 1 'for debugging purposes
Debug.Print(intCount.ToString & vbNewLine & element.innerHTML)
'strLine1 = element.children(0).innerText
'strLine2 = element.children(1).innerText
'and so on...
Next
I assume that by this point you've already resolved the problem one way or another, but I thought I'd suggest the HtmlAgilityPack. It has the advantage of being written to support just this type of scenario, and (as far as I know) is a native .NET library rather than being COM-based. It might be a better fit for your situation.

Need a quick and simple classic asp page to query records from a sql server database

I know this seems elementary, but I have been looking for 2 days and all i find is snippets that dont work. I am simply trying to have a web page dynamically display the contents of a table with 4 columns.
Need by tomorrow!
Help!
Thank you!
Here's the simplest way to do it. This is assuming your server is SQL Server. If not, head to http://connectionstrings.com and look up the specifics for your server. That site is awesome and I find myself on it all the time.
set rs = server.CreateObject("ADODB.Recordset")
rs.open "select col1 from table1", "provider=sqloledb.1;uid=user;pwd=password;database=database;Server=server;"
do while rs.EOF = false
response.write rs("col1")
rs.MoveNext
loop
What's going on here is we're using Microsoft's ADO database library. I'm creating a Recordset object and calling its open method. Provided to the open method are the sql statement I want to execute and the specifics on how to connect to that database. The specifics on how to connect to the database is commonly referred to as a "Connection String." The site mentioned above is an invaluable resource in figuring out exactly what this should look like. 99% of the time, any problems I've run into have been an invalid connection string. Once opened, I loop through the returned records in the while loop and write out the data to the page.
DON'T FORGET THE CALL TO rs.MoveNext!!! I've done this a handful of times over the years and you'll wind up with an infinite loop.

How to delay a response in Classic ASP

I have a site running Classic-ASP and on the login page I would like to delay the response to a failed login attempt (by like 10 seconds) to help prevent brute force attacks on accounts.
Quick google searches show some hacks using SQL server queries that seem hack-tastic.
Is there a good way to do this in classic asp?
I am not going to answer your specific question, as many have already done so, but there are far better ways of preventing brute force attacks.
For instance:
Why not lock a specific session or IP address out after say 5 (being generous here) failed login attempts? You could lock it out for say 10 minutes. You could even write a "401 Unauthorized" HTTP status and then simply end the response with Response.End.
In a similar fashion, but not even linked to failed logins, you could block requests for the login page more than X times in Y seconds for a specific IP, UserAgent and other client features - ensuring kind of a 'unique' client.
Ignore IP address (it is easily spoofed and can be a proxy server IP), and simply detect the automation of the login attempt. X number of failed logins within Y seconds for a specific username/email address, block it for that username for a set period of time, and end the response.
Just saying there are other options than putting unnecessary load on your server by locking some resources and waiting.
Obviously, doing this at the hardware layer - firewalls etc. would be the preferred option.
There is another approach, but keep in mind the aforementioned caveats about unessecarily consumed resources. Here is an approach though
Sub DelayResponse(numberOfseconds)
Dim WshShell
Set WshShell=Server.CreateObject("WScript.Shell")
WshShell.Run "waitfor /T " & numberOfSecond & "SignalThatWontHappen", , True
End Sub
There is the WScript.Sleep method for general purpose VBScript, however, this won't work in the context of ASP.
There are a number of other mechanisms you can use to achieve this, however, they're all effectively "workarounds" as there's no built-in way to cleanly cause an ASP page (running VBScript) to pause itself.
See here:
How do I make my ASP page pause or 'sleep'?
To specifically answer your question of:
Is there a good way to do this in
classic asp?
No. There's no good way to do this, and there's only the "hack-tastic" hacks that can be used, however they bring with them all sorts of side-effects and caveats. (See the last part of the "How do I make my ASP page pause or 'sleep'?" link for a specific memory eating, page faulting nasty side-effect.)
You can use :
<html>
<head>
<title>Sleep</title>
</head>
<body>
<%
function Sleep(seconds)
set oShell = CreateObject("Wscript.Shell")
cmd = "%COMSPEC% /c timeout " & seconds & " /nobreak"
oShell.Run cmd,0,1
End function
Sleep(5)
response.write("End")
%>
</body>
</html>
There is no simple way to do so in pure ASP.
Either SQL WAITFOR, or create simple ActiveX component in VB (or anything) that sleeps.
Note that this will increase load on the server. Sleeping requests keep memory and connections consumed for nothing.
<%
set shell = CreateObject("WScript.Shell")
t1 = timer()
sleep(5)
t2 = timer()
response.write "waited "& t2-t1 &" secs"
function sleep(seconds)
if seconds>=1 then shell.popup "pausing",seconds,"pause",64
end function
%>
Other approach to avoid brute force attacks without using IP restrictions is to offer a captcha after the second fail attempt for the same user. This is the way Google do it.
There is the Response.Buffer option that you can use to tell it to delay returning the response until the page has completed processing, so you could perhaps combine that with some kind of timeout in the script, but it would not be especially elegant especially as VBScript doesn't really offer you a way of asking threads to sleep so you can end up thrashing the CPU.
Maybe better to use a server-side session and javascript on the client, so the client delays the request and the server will only send the response after the expected delay is over. That should provide some server-side safeguards and be useable for users who aren't trying to mess around with your system...
I'm using this:
function sleep(scs)
Dim lo_wsh, ls_cmd
Set lo_wsh = CreateObject( "WScript.Shell" )
ls_cmd = "%COMSPEC% /c ping -n " & 1 + scs & " 127.0.0.1>nul"
lo_wsh.Run ls_cmd, 0, True
End Function
sleep(5) 'wait for 5 seconds
Bye :-)
For those using MySQL, you can do the following :
Sub SleepMysql(n)
'Define Query
Dim SqlStr : SqlStr = "DO SLEEP(" & n & ")"
'Run Query
Dim rsTemp : Set rsTemp = YourDatabaseConnection.Execute(SqlStr)
'Release resources
Set rsTemp = Nothing
End Sub 'SleepMysql
Of all of the ideas - I liked Dercsár's answer - here's my implementation of a wait that I found works well. - requires your application to have access to a SQL server:
<%
' ASP Script to example to wait
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open strConnString ' your connection to sql server here...
Response.write("Waiting for 15 seconds...." & now() & "<BR>")
Call subWaitTime("00:00:15")
' Wait 15 seconds
Response.write("ok - Done" & now() & "<BR>")
conn.close
set conn = nothing
'---- Utility sub to wait
Sub subWaitTime(sTime)
' Call subWaitTime with number of Hours:Minutes:Seconds to delay
sqlWaitTime = "WAITFOR DELAY " & fnSqlStr(sTime)
conn.Execute(sqlWaitTime) '
End Sub
%>
In response to the “delay the response” part of your question:
dim SecondsToWait : SecondsToWait = 4
dim StartTime : StartTime = Time()
Do Until
DateDiff("s", StartTime, Time(), 0, 0) > SecondsToWait
Loop
Pure Classic ASP without SQL and WScript Shell, but for debug delay purposes only. This is a snippet for testing (very helupful), but it does not address the “good way” part of your question
This answer for the sake of completeness and for people looking for (debug) delays. Failed login attempts should not be handled like this.
Another way you can delay asp response by using this code.
Sub MyDelay(NumberOfSeconds)
Dim DateTimeResume
DateTimeResume= DateAdd("s", NumberOfSeconds, Now())
Do Until (Now() > DateTimeResume)
Loop
End Sub
Call this function by using this code.
Call MyDelay(5)
Just redirect to a randomly named large image that takes the desired amount of seconds to load.

Resources