As I've started in the title, while I'm querying for user data in my java application, I get following message: "Operation not allowed after ResultSet closed".
I know that this is happens if you try to have more ResultSets opened at the same time.
Here is my current code:
App calls getProject("..."), other 2 methods are there just for help. I'm using 2 classes because there is much more code, this is just one example of exception I get.
Please note that I've translated variable names, etc. for better understanding, I hope I didn't miss anything.
/* Class which reads project data */
public Project getProject(String name) {
ResultSet result = null;
try {
// executing query for project data
// SELECT * FROM Project WHERE name=name
result = statement.executeQuery(generateSelect(tProject.tableName,
"*", tProject.name, name));
// if cursor can't move to first place,
// that means that project was not found
if (!result.first())
return null;
return user.usersInProject(new Project(result.getInt(1), result
.getString(2)));
} catch (SQLException e) {
e.printStackTrace();
return null;
} catch (BadAttributeValueExpException e) {
e.printStackTrace();
return null;
} finally {
// closing the ResultSet
try {
if (result != null)
result.close();
} catch (SQLException e) {
}
}
}
/* End of class */
/* Class which reads user data */
public Project usersInProject(Project p) {
ResultSet result = null;
try {
// executing query for users in project
// SELECT ID_User FROM Project_User WHERE ID_Project=p.getID()
result = statement.executeQuery(generateSelect(
tProject_User.tableName, tProject_User.id_user,
tProject_User.id_project, String.valueOf(p.getID())));
ArrayList<User> alUsers = new ArrayList<User>();
// looping through all results and adding them to array
while (result.next()) { // here java gets ResultSet closed exception
int id = result.getInt(1);
if (id > 0)
alUsers.add(getUser(id));
}
// if no user data was read, project from parameter is returned
// without any new user data
if (alUsers.size() == 0)
return p;
// array of users is added to the object,
// then whole object is returned
p.addUsers(alUsers.toArray(new User[alUsers.size()]));
return p;
} catch (SQLException e) {
e.printStackTrace();
return p;
} finally {
// closing the ResultSet
try {
if (result != null)
result.close();
} catch (SQLException e) {
}
}
}
public User getUser(int id) {
ResultSet result = null;
try {
// executing query for user:
// SELECT * FROM User WHERE ID=id
result = statement.executeQuery(generateSelect(tUser.tableName,
"*", tUser.id, String.valueOf(id)));
if (!result.first())
return null;
// new user is constructed (ID, username, email, password)
User usr = new user(result.getInt(1), result.getString(2),
result.getString(3), result.getString(4));
return usr;
} catch (SQLException e) {
e.printStackTrace();
return null;
} catch (BadAttributeValueExpException e) {
e.printStackTrace();
return null;
} finally {
// closing the ResultSet
try {
if (result != null)
result.close();
} catch (SQLException e) {
}
}
}
/* End of class */
Statements from both classes are added in constructor, calling connection.getStatement() when constructing each of the classes.
tProject and tProject_User are my enums, I'm using it for easier name handling. generateSelect is my method and should work as expected. I'm using this because I've found out about prepared statements after I have written most of my code, so I left it as it is.
I am using latest java MySQL connector (5.1.21).
I don't know what else to try. Any advice will be appreciated.
Quoting from #aroth's answer:
There are many situations in which a ResultSet will be automatically closed for you. To quote the official documentation:
http://docs.oracle.com/javase/6/docs/api/java/sql/ResultSet.html
A ResultSet object is automatically closed when the Statement object that generated
it is closed, re-executed, or used to retrieve the next result from a sequence of
multiple results.
Here in your code , You are creating new ResultSet in the method getUser using the same Statement object which created result set in the usersInProject method which results in closing your resultset object in the method usersInProject.
Solution:
Create another statement object and use it in getUser to create resultset.
It's not really possible to say definitively what is going wrong without seeing your code. However note that there are many situations in which a ResultSet will be automatically closed for you. To quote the official documentation:
A ResultSet object is automatically closed when the Statement object
that generated it is closed, re-executed, or used to retrieve the next
result from a sequence of multiple results.
Probably you've got one of those things happening. Or you're explicitly closing the ResultSet somewhere before you're actually done with it.
Also, have you considered using an ORM framework like Hibernate? In general something like that is much more pleasant to work with than the low-level JDBC API.
Related
I wrote the following method as an onClick handler. First and second click, I got result from DB. By the third time, the code stopped in the "getFeatures(trgtFilter)" line and didn't return. In debug mode, I saw that it is waiting for DB connection. Can someone tell me what I did wrong? I'm using GeoTools 15 and Oracle 12.
private Geometry getNewGeometry(String refID) throws Exception {
if (trgLayer != null) {
Connection con = null;
OracleConnection oraCon=null;
FeatureIterator<SimpleFeature> itr = null;
try {
con = ((JDBCDataStore) srcLayer.getFeatureSource().getDataStore()).getConnection(Transaction.AUTO_COMMIT);
oraCon = (OracleConnection) new DelegatingConnection(con).getInnermostDelegate();
Filter trgtFilter = editTask.getConfiguration().getReferenceFilter(trgLayer, refID);
FeatureCollection fc = trgLayer.getFeatureSource().getFeatures(trgtFilter);
itr = fc.features();
if (!itr.hasNext())
return null;
...
} catch (Exception e) {
throw e;
} finally {
if (itr != null)
itr.close();
if (oraCon != null) {
try {
oraCon.close();
if (con != null && !con.isClosed())
con.close();
} catch (SQLException e) {
LOGGER.error("", e);
}
}
}
}
}
If the filter is an 'id' filter, it could be that there is no index on that column in the Oracle table. If that's the case, the database will do a full-table scan.
Assuming you have a geospatial index and assuming the user is 'zoomed-in' on a given area, you could add the user viewport's geo-bounds to the query. With that query, the database can use the geo-index.
Alternatively, you can create an index on the fid/id column for the table if look-ups by feature id are going to be common.
I have few tables with big amount of data (about 100 million records). So I can't store this data in memory but I would like to stream this result set using java.util.stream class and pass this stream to another class. I read about Stream.of and Stream.Builder operators but they are buffered streams in memory. So is there any way to resolve this question?
UPDATE #1
Okay I googled and found jooq library. I'm not sure but looks like it could be applicable to my test case. To summarize I have few tables with big amount of data. I would like to stream my resultset and transfer this stream to another method. Something like this:
// why return Stream<String>? Because my result set has String type
private Stream<Record> writeTableToStream(DataSource dataSource, String table) {
Stream<Record> record = null;
try (Connection connection = dataSource.getConnection()) {
String sql = "select * from " + table;
try (PreparedStatement pSt = connection.prepareStatement(sql)) {
connection.setAutoCommit(false);
pSt.setFetchSize(5000);
ResultSet resultSet = pSt.executeQuery();
//
record = DSL.using(connection)
.fetch(resultSet).stream();
}
} catch (SQLException sqlEx) {
logger.error(sqlEx);
}
return record;
}
Could please someone advise, am I on correct way? Thanks.
UPDATE #2
I made some experiment on jooq and could say now that above decision is not suitable for me. This code record = DSL.using(connection).fetch(resultSet).stream(); takes too much time
The first thing you have to understand is that code like
try (Connection connection = dataSource.getConnection()) {
…
try (PreparedStatement pSt = connection.prepareStatement(sql)) {
…
return stream;
}
}
does not work as by the time you leave the try blocks, the resources are closed while the processing of the Stream hasn’t even started.
The resource management construct “try with resources” works for resources used within a block scope inside a method but you are creating a factory method returning a resource. Therefore you have to ensure that the closing of the returned stream will close the resources and the caller is responsible for closing the Stream.
Further, you need a function which produces an item out of a single line from the ResultSet. Supposing, you have a method like
Record createRecord(ResultSet rs) {
…
}
you may create a Stream<Record> basically like
Stream<Record> stream = StreamSupport.stream(new Spliterators.AbstractSpliterator<Record>(
Long.MAX_VALUE,Spliterator.ORDERED) {
#Override
public boolean tryAdvance(Consumer<? super Record> action) {
if(!resultSet.next()) return false;
action.accept(createRecord(resultSet));
return true;
}
}, false);
But to do it correctly you have to incorporate the exception handling and closing of resources. You can use Stream.onClose to register an action that will be performed when the Stream gets closed, but it has to be a Runnable which can not throw checked exceptions. Similarly the tryAdvance method is not allowed to throw checked exceptions. And since we can’t simply nest try(…) blocks here, the program logic of suppression exceptions thrown in close, when there is already a pending exception, doesn’t come for free.
To help us here, we introduce a new type which can wrap closing operations which may throw checked exceptions and deliver them wrapped in an unchecked exception. By implementing AutoCloseable itself, it can utilize the try(…) construct to chain close operations safely:
interface UncheckedCloseable extends Runnable, AutoCloseable {
default void run() {
try { close(); } catch(Exception ex) { throw new RuntimeException(ex); }
}
static UncheckedCloseable wrap(AutoCloseable c) {
return c::close;
}
default UncheckedCloseable nest(AutoCloseable c) {
return ()->{ try(UncheckedCloseable c1=this) { c.close(); } };
}
}
With this, the entire operation becomes:
private Stream<Record> tableAsStream(DataSource dataSource, String table)
throws SQLException {
UncheckedCloseable close=null;
try {
Connection connection = dataSource.getConnection();
close=UncheckedCloseable.wrap(connection);
String sql = "select * from " + table;
PreparedStatement pSt = connection.prepareStatement(sql);
close=close.nest(pSt);
connection.setAutoCommit(false);
pSt.setFetchSize(5000);
ResultSet resultSet = pSt.executeQuery();
close=close.nest(resultSet);
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Record>(
Long.MAX_VALUE,Spliterator.ORDERED) {
#Override
public boolean tryAdvance(Consumer<? super Record> action) {
try {
if(!resultSet.next()) return false;
action.accept(createRecord(resultSet));
return true;
} catch(SQLException ex) {
throw new RuntimeException(ex);
}
}
}, false).onClose(close);
} catch(SQLException sqlEx) {
if(close!=null)
try { close.close(); } catch(Exception ex) { sqlEx.addSuppressed(ex); }
throw sqlEx;
}
}
This method wraps the necessary close operation for all resources, Connection, Statement and ResultSet within one instance of the utility class described above. If an exception happens during the initialization, the close operation is performed immediately and the exception is delivered to the caller. If the stream construction succeeds, the close operation is registered via onClose.
Therefore the caller has to ensure proper closing like
try(Stream<Record> s=tableAsStream(dataSource, table)) {
// stream operation
}
Note that also the delivery of an SQLException via RuntimeException has been added to the tryAdvance method. Therefore you may now add throws SQLException to the createRecord method without problems.
jOOQ
I'm going to answer the jOOQ part of your question. As of jOOQ 3.8, there have now been quite a few additional features related to combining jOOQ with Stream. Other usages are also documented on this jOOQ page.
Your suggested usage:
You tried this:
Stream<Record> stream = DSL.using(connection).fetch(resultSet).stream();
Indeed, this doesn't work well for large result sets because fetch(ResultSet) fetches the entire result set into memory and then calls Collection.stream() on it.
Better (lazy) usage:
Instead, you could write this:
try (Stream<Record> stream = DSL.using(connection).fetchStream(resultSet)) {
...
}
... which is essentially convenience for this:
try (Cursor<Record> cursor = DSL.using(connection).fetchLazy(resultSet)) {
Stream<Record> stream = cursor.stream();
...
}
See also DSLContext.fetchStream(ResultSet)
Of course, you could also let jOOQ execute your SQL string, rather than wrestling with JDBC:
try (Stream<Record> stream =
DSL.using(dataSource)
.resultQuery("select * from {0}", DSL.name(table)) // Prevent SQL injection
.fetchSize(5000)
.fetchStream()) {
...
}
The dreaded SELECT *
As was criticised in the comments, their jOOQ usage seemed slow because of how jOOQ eagerly fetches LOB data into memory despite using fetchLazy(). The word "lazy" corresponds to fetching records lazily (one by one), not fetching column data lazily. A record is completely fetched in one go, assuming you actually want to project the entire row.
If you don't need some heavy rows, don't project them! SELECT * is almost always a bad idea in SQL. Drawbacks:
It causes a lot more I/O and memory overhead in the database server, the network, and the client.
It prevents covering index usage
It prevents join elimination transformations
More info in this blog post here.
On try-with-resources usage
Do note that a Stream produced by jOOQ is "resourceful", i.e. it contains a reference to an open ResultSet (and PreparedStatement). So, if you really want to return that stream outside of your method, make sure it is closed properly!
I'm not aware of any well-known library that will do it for you.
That said, this article shows how to wrap the resultset with an Iterator (ResultSetIterator) and pass it as the first parameter to Spliterators.spliteratorUnknownSize() in order to create a Spliterator.
The Spliterator can then be used by StreamSupport in order to create a Stream on top of it.
Their suggested implementation of ResultSetIterator class:
public class ResultSetIterator implements Iterator {
private ResultSet rs;
private PreparedStatement ps;
private Connection connection;
private String sql;
public ResultSetIterator(Connection connection, String sql) {
assert connection != null;
assert sql != null;
this.connection = connection;
this.sql = sql;
}
public void init() {
try {
ps = connection.prepareStatement(sql);
rs = ps.executeQuery();
} catch (SQLException e) {
close();
throw new DataAccessException(e);
}
}
#Override
public boolean hasNext() {
if (ps == null) {
init();
}
try {
boolean hasMore = rs.next();
if (!hasMore) {
close();
}
return hasMore;
} catch (SQLException e) {
close();
throw new DataAccessException(e);
}
}
private void close() {
try {
rs.close();
try {
ps.close();
} catch (SQLException e) {
//nothing we can do here
}
} catch (SQLException e) {
//nothing we can do here
}
}
#Override
public Tuple next() {
try {
return SQL.rowAsTuple(sql, rs);
} catch (DataAccessException e) {
close();
throw e;
}
}
}
and then:
public static Stream stream(final Connection connection,
final String sql,
final Object... parms) {
return StreamSupport
.stream(Spliterators.spliteratorUnknownSize(
new ResultSetIterator(connection, sql), 0), false);
}
Here is the simplest sample by abacus-jdbc.
final DataSource ds = JdbcUtil.createDataSource(url, user, password);
final SQLExecutor sqlExecutor = new SQLExecutor(ds);
sqlExecutor.stream(sql, parameters).filter(...).map(...).collect(...) // lazy execution&loading and auto-close Statement/Connection
Or:
JdbcUtil.prepareQuery(ds, sql)
.stream(ResultRecord.class) // or RowMapper.MAP/...
.filter(...).map(...).collect(...) // lazy execution&loading and auto-close Statement/Connection
This is totally lazy loading and auto-closure. The records will loaded from db by fetch size (default if not specified) and the Statement and Connection will automatically closed after the result/records are collected.
Disclosure: I'm the developer of AbacusUtil.
Using my library it would be done like this:
attach maven dependency:
<dependency>
<groupId>com.github.buckelieg</groupId>
<artifactId>db-fn</artifactId>
<version>0.3.4</version>
</dependency>
use library in code:
Function<Stream<I>, O> processor = stream -> //process input stream
try (DB db = new DB("jdbc:postgresql://host:port/database?user=user&password=pass")) {
processor.apply(
db.select("SELECT * FROM my_table t1 JOIN my_table t2 ON t1.id = t2.id")
.fetchSize(5000)
.execute(rs -> /*ResultSet mapper*/)
);
}
See more here
Some common module called Tools of a Ujorm framework offers a simple solution using the RowIterator class.
Example of use:
PreparedStatement ps = dbConnection.prepareStatement("SELECT * FROM myTable");
new RowIterator(ps).toStream().forEach((RsConsumer)(resultSet) -> {
int value = resultSet.getInt(1);
});
Maven dependency on the Tools library (50KB):
<dependency>
<groupId>org.ujorm</groupId>
<artifactId>ujo-tools</artifactId>
<version>1.93</version>
</dependency>
See jUnit test for more information.
I just did the summary to provide the real example about how to stream ResultSet and do the simple SQL query without using 3rd
click here for detail
Blockquote: Java 8 provided the Stream family and easy operation of it. The way of pipeline usage made the code clear and smart.
However, ResultSet is still go with very legacy way to process. Per actual ResultSet usage, it is really helpful if converted as Stream.
....
StreamUtils.uncheckedConsumer is required to convert the the SQLException to runtimeException to make the Lamda clear.
I currently am working on a project that does a lot of work with Database.
One core idiom that I have reused many, many times in my code is the following.
My question is, is there a better way to handle the exceptions at each step of the getTransformedResults method? Is this a proper way of handling the SQLExceptions, or is there a better, more concise way of doing this?
Thanks for your input!
public ResultType handleResultSet(ResultSet rs);
public ResultType getTransformedResults(String query) throws SQLException {
ResultType resultObj = new ResultType();
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException sqle) {
// cleanup
throw sqle;
}
Statement stmt = null;
try {
stmt = connection.createStatement();
} catch (SQLException sqle) {
try { connection.close() } catch (SQLException dontCare) {}
// cleanup
throw sqle;
}
ResultSet rs = null;
try {
ResultSet rs = stmtm.executeQuery(query);
resultObj = handleResultSet(rs);
} catch (SQLException sqle) {
// cleanup
throw sqle;
} finally {
if (rs != null) try { rs.close() } catch (SQLException dontCare) {}
try { stmt.close() } catch (SQLException dontCare) {}
try { connection.close() } catch (SQLException dontCare) {}
}
return resultObj;
}
Java 7 has some constructs you might appreciate, I think you can use try/finally without catch (which mimics your catch and rethrow).
Also, since you've caught and handled the SQL exception, perhaps you should re-throw it as something else--perhaps as a runtime exception--this makes it easier to catch all runtime exceptions at a primary entry point rather than having to deal with exceptions every single time you access the DB.
Personally I might handle this by passing in an interface implementation rather than subclassing.
Ultimately, if you're only handling the exceptions in that method, and not polluting the mainline code, what else can you really do, and what would be the point of doing it? You might make each step a bit more granular so it's not all in one method, but other than that...
You might consider an application-specific exception, which may make testing and configuration cleaner, but that depends on context.
Clarification of interface idea
Instead of subclassing you'd have an interface that implemented the handling of result sets and query string retrieval, so two methods--one for the query, one for the results.
You'd pass an implementation to an instance of mostly what you have now, but it takes the interface instead of a query string. The rest of the code is essentially identical, but it gets the query string from the interface impl, and calls the interface impl's result handling method, saving the result until the cleanup.
It's essentially the same as you have now, but IMO cleaner since any class could implement the interface, including anonymous classes, or other classes in your domain.
You may be interested in using Apache Commons DbUtils which is aimed exactly at such purposes.
It has some drawbacks when trying to use more sophisticated JDBC but for regular usage it should be more than enough.
Besides that, your code contains too much try/catch blocks and can be simplified to something like the following:
public interface ResultSetHandler<ResultType> {
ResultType handleResultSet(ResultSet rs);
}
public <ResultType> ResultType getTransformedResults(String query, ResultSetHandler<ResultType> rsh) throws SQLException {
Connection connection = null;
Statement stmt = null;
try {
connection = dataSource.getConnection();
stmt = connection.createStatement();
ResultSet rs = stmtm.executeQuery(query);
return rsh.handleResultSet(rs);
} catch (SQLException sqle) {
// cleanup
throw sqle;
} finally {
if(stmt != null) {
statement.close(); // closes also resultSet
connection.close();
}
}
}
Though Apache Commons DbUtils library does exactly the same under the hood.
org.springframework.jdbc.core.JdbcTemplate - "...simplifies the use of JDBC and helps to avoid common errors."
Connection c = null;
Statement s = null;
ResultSet r = null;
try {
c = datasource.getConnection();
s = c.createStatement();
r = s.executeQuery(sql);
rsh.handleResultSet(r);
}
finally {
DbUtils.closeQuietly(r);
DbUtils.closeQuietly(s);
DbUtils.closeQuietly(c);
}
Note that DbUtils is apaache commons-dbutils, and the closeQuietly is equivalent to:
try {
c.close();
}
catch (SQLException e) {
}
This all being said, i'd recommend using spring's jdbc features:
JdbcTemplate template = new JdbcTemplate(dataSource);
List data = template.query(sql, new RowMapper() { ... });
The RowMapper is an interface whose implementation has the job of converting the current position in the resultset to an object. So by simply giving it the logic of what to do with one row, you automatically collect the list of the objects for all rows in these two lines of code plus whatever it takes to map the row. There's other methods which let you work with the ResultSet in different ways, but this is a pretty standard way in which people use it.
All the connection and statement management is done for you, and you don't have to worry about resource management at all.
I use this code to fetch data from database table.
public List<Dashboard> getDashboardList() throws SQLException {
if (ds == null) {
throw new SQLException("Can't get data source");
}
//get database connection
Connection con = ds.getConnection();
if (con == null) {
throw new SQLException("Can't get database connection");
}
PreparedStatement ps = con.prepareStatement(
"SELECT * from GLOBALSETTINGS");
//get customer data from database
ResultSet result = ps.executeQuery();
List<Dashboard> list = new ArrayList<Dashboard>();
while (result.next()) {
Dashboard cust = new Dashboard();
cust.setUser(result.getString("SessionTTL"));
cust.setPassword(result.getString("MAXACTIVEUSERS"));
//store all data into a List
list.add(cust);
}
return list;
}
This code is a part of a JSF page which is deployed on glassfish server. The problem is that when I reload the JSF page many times(around 8 times) the web page freezes. I suspect that the thread pool is fill and there is no space for new connections. How I can solve the problem? Close the connection when the query is finished or there is another way?
Best wishes
First of all: Yes you should close your connection when your done by explicitly calling the close() method. Closing a connection will release database resources.
UPDATE: And you should close the PreparedStatement as well (with close()). I would also recommend to handle SQLExceptions in your method and not throw it, since you need to make sure that your statement and connection are closed even if an exception occurs.
Something like this:
Connection connection = dataSource.getConnection();
try {
PreparedStatement statement = connection.prepareStatement();
try {
// Work with the statement
catch (SQLException e ) {
// Handle exceptions
} catch (SQLException e {
// Handle exceptions
} finally {
statement.close();
}
} finally {
connection.close();
}
Furthermore, you should not query the database in a bean field's getter method. Getters can be called several times during each request. The more elegant way would be to prepare the DashboardList in the constructor or #PostConstruct of your bean.
In my application I have implemented a method to get favourits of particular user. If the user is a new one there will not be a entry in the table.If so I add default favourtis to the table. Code is shown below.
public String getUserFavourits(String username) {
String s = "SELECT FAVOURITS FROM USERFAVOURITS WHERE USERID='" +
username.trim() + "'";
String a = "";
Statement stm = null;
ResultSet reset = null;
DatabaseConnectionHandler handler = null;
Connection conn = null;
try {
handler = DatabaseConnectionHandler.getInstance();
conn = handler.getConnection();
stm = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
reset = stm.executeQuery(s);
if (reset.next()) {
a = reset.getString("FAVOURITS").toString();
}
reset.close();
stm.close();
}
catch (SQLException ex) {
ex.printStackTrace();
}
catch (Exception ex) {
ex.printStackTrace();
}
finally {
try {
handler.returnConnectionToPool(conn);
if (stm != null) {
stm.close();
}
if (reset != null) {
reset.close();
}
}catch (Exception ex) {
ex.printStackTrace();
}
}
if (a.equalsIgnoreCase("")) {
a = updateNewUserFav(username);
}
return a;
}
You can see that after the Finally block updateNewUserFav(username) method is use to insert default favourits in to table. Normally users are forced to change this in their first login.
My problem is many users have complain me about they hava lost their customized favourits and default has get loaded in their login. When I go through the code I notice that it can only happen if exception occured in the try block. When I debug code works fine. Is this can be coused at time when DB is busy?
Normally there are more than 1000 concurrent user in the system. Since it is real time application there will be huge number a of request comming to the Database(DB is Oracle).
Can some one pls explain.
Firstly, use jonearles suggestion about bind variables. If a lot of your code is like this, with 1000 concurrent users, I'd hate to think what performance is like.
Secondly, if it is busy then there is a chance of time-outs. As you say, if an exception is encountered then it falls back to the "updateNewUserFav"
Really, it should only call that if NO exception is raised.
If an exception is raised, the function should fail. The current code is similar to
"TURN THE IGNITION KEY TO START THE CAR"
"IF THERE IS A PROBLEM, RING GARAGE AND BOOK APPOINTMENT"
"PUT CAR INTO GEAR AND RELEASE HAND_BRAKE"
You really only want to release the hand-brake once the car has successfully started, otherwise you'll end up rolling down the hill until the sudden stop at the end (often involving an expensive CRUNCH sound).